Compare commits

...

13 Commits

68 changed files with 5173 additions and 2184 deletions

148
Cargo.lock generated
View File

@@ -41,6 +41,12 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "allocator-api2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
[[package]] [[package]]
name = "anes" name = "anes"
version = "0.1.6" version = "0.1.6"
@@ -276,6 +282,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "boxing"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a817f12ef805b34fe1565bea00630d84f8f08bf26200b05c41456c77cdada88"
dependencies = [
"sptr",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.1" version = "1.12.1"
@@ -289,9 +304,12 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.1" version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
dependencies = [
"allocator-api2 0.2.21",
]
[[package]] [[package]]
name = "bytes" name = "bytes"
@@ -491,6 +509,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@@ -516,15 +543,6 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "convert_case"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "cooked-waker" name = "cooked-waker"
version = "5.0.0" version = "5.0.0"
@@ -835,7 +853,7 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [ dependencies = [
"convert_case 0.10.0", "convert_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
@@ -959,6 +977,27 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "env_filter"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f"
dependencies = [
"log",
]
[[package]]
name = "env_logger"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"log",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -1228,6 +1267,30 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "gc-arena"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd70cf88a32937834aae9614ff2569b5d9467fa0c42c5d7762fd94a8de88266"
dependencies = [
"allocator-api2 0.2.21",
"gc-arena-derive",
"hashbrown 0.14.5",
"sptr",
]
[[package]]
name = "gc-arena-derive"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c612a69f5557a11046b77a7408d2836fe77077f842171cd211c5ef504bd3cddd"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -1278,6 +1341,12 @@ dependencies = [
"wasip3", "wasip3",
] ]
[[package]]
name = "ghost-cell"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7"
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.32.3" version = "0.32.3"
@@ -1315,6 +1384,9 @@ name = "hashbrown"
version = "0.14.5" version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"allocator-api2 0.2.21",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
@@ -1331,7 +1403,7 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2 0.2.21",
"equivalent", "equivalent",
"foldhash 0.2.0", "foldhash 0.2.0",
] ]
@@ -2020,10 +2092,14 @@ dependencies = [
name = "nix-js" name = "nix-js"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"allocator-api2 0.4.0",
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
"boxing",
"bumpalo",
"bzip2", "bzip2",
"clap", "clap",
"colored",
"criterion", "criterion",
"deno_core", "deno_core",
"deno_error", "deno_error",
@@ -2032,6 +2108,8 @@ dependencies = [
"ere", "ere",
"fastwebsockets", "fastwebsockets",
"flate2", "flate2",
"gc-arena",
"ghost-cell",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"hex", "hex",
"http", "http",
@@ -2043,7 +2121,6 @@ dependencies = [
"miette", "miette",
"mimalloc", "mimalloc",
"nix-compat", "nix-compat",
"nix-js-macros",
"nix-nar", "nix-nar",
"num_enum", "num_enum",
"regex", "regex",
@@ -2057,10 +2134,12 @@ dependencies = [
"serde_json", "serde_json",
"sha1", "sha1",
"sha2", "sha2",
"smallvec",
"string-interner", "string-interner",
"tap", "tap",
"tar", "tar",
"tempfile", "tempfile",
"test-log",
"thiserror 2.0.18", "thiserror 2.0.18",
"tokio", "tokio",
"toml", "toml",
@@ -2070,15 +2149,6 @@ dependencies = [
"xz2", "xz2",
] ]
[[package]]
name = "nix-js-macros"
version = "0.1.0"
dependencies = [
"convert_case 0.11.0",
"quote",
"syn",
]
[[package]] [[package]]
name = "nix-nar" name = "nix-nar"
version = "0.3.1" version = "0.3.1"
@@ -3149,6 +3219,12 @@ dependencies = [
"der", "der",
] ]
[[package]]
name = "sptr"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
[[package]] [[package]]
name = "sqlite-wasm-rs" name = "sqlite-wasm-rs"
version = "0.5.2" version = "0.5.2"
@@ -3393,6 +3469,28 @@ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
[[package]]
name = "test-log"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4"
dependencies = [
"env_logger",
"test-log-macros",
"tracing-subscriber",
]
[[package]]
name = "test-log-macros"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "text-size" name = "text-size"
version = "1.1.1" version = "1.1.1"
@@ -3546,9 +3644,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.9.12+spec-1.1.0" version = "0.9.9+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" checksum = "eb5238e643fc34a1d5d7e753e1532a91912d74b63b92b3ea51fde8d1b7bc79dd"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde_core", "serde_core",

View File

@@ -1,8 +1,7 @@
[workspace] [workspace]
resolver = "3" resolver = "3"
members = [ members = [
"nix-js", "nix-js"
"nix-js-macros"
] ]
[profile.profiling] [profile.profiling]

View File

@@ -1,12 +0,0 @@
[package]
name = "nix-js-macros"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
convert_case = "0.11"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

View File

@@ -1,252 +0,0 @@
//! Implements the `ir!` procedural macro.
//!
//! This macro is designed to reduce the boilerplate associated with defining
//! an Intermediate Representation (IR) that follows a specific pattern. It generates:
//! 1. An enum representing the different kinds of IR nodes.
//! 2. Structs for each of the variants that have fields.
//! 3. `Ref` and `Mut` versions of the main enum for ergonomic pattern matching on references.
//! 4. `From` implementations to easily convert from a struct variant (e.g., `BinOp`) to the main enum (`Ir::BinOp`).
//! 5. A `To[IrName]` trait to provide a convenient `.to_ir()` method on the variant structs.
use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
FieldsNamed, Ident, Token, Type, parenthesized,
parse::{Parse, ParseStream, Result},
punctuated::Punctuated,
token,
};
/// Represents one of the variants passed to the `ir!` macro.
pub enum VariantInput {
/// A unit-like variant, e.g., `Arg`.
Unit(Ident),
/// A tuple-like variant with one unnamed field, e.g., `ExprRef(ExprId)`.
Tuple(Ident, Type),
/// A struct-like variant with named fields, e.g., `BinOp { lhs: ExprId, rhs: ExprId, kind: BinOpKind }`.
Struct(Ident, FieldsNamed),
}
/// The top-level input for the `ir!` macro.
pub struct MacroInput {
/// The name of the main IR enum to be generated (e.g., `Ir`).
pub base_name: Ident,
/// The list of variants for the enum.
pub variants: Punctuated<VariantInput, Token![,]>,
}
impl Parse for VariantInput {
fn parse(input: ParseStream) -> Result<Self> {
let name: Ident = input.parse()?;
if input.peek(token::Paren) {
// Parse a tuple-like variant: `Variant(Type)`
let content;
parenthesized!(content in input);
let ty: Type = content.parse()?;
if !content.is_empty() {
return Err(content.error("Expected a single type inside parentheses"));
}
Ok(VariantInput::Tuple(name, ty))
} else if input.peek(token::Brace) {
// Parse a struct-like variant: `Variant { field: Type, ... }`
let fields: FieldsNamed = input.parse()?;
Ok(VariantInput::Struct(name, fields))
} else {
// Parse a unit-like variant: `Variant`
Ok(VariantInput::Unit(name))
}
}
}
impl Parse for MacroInput {
fn parse(input: ParseStream) -> Result<Self> {
// The macro input is expected to be: `IrName, Variant1, Variant2, ...`
let base_name = input.parse()?;
input.parse::<Token![,]>()?;
let variants = Punctuated::parse_terminated(input)?;
Ok(MacroInput {
base_name,
variants,
})
}
}
/// The implementation of the `ir!` macro.
pub fn ir_impl(input: TokenStream) -> TokenStream {
let parsed_input = syn::parse_macro_input!(input as MacroInput);
let base_name = &parsed_input.base_name;
let ref_name = format_ident!("{}Ref", base_name);
let mut_name = format_ident!("{}Mut", base_name);
let to_trait_name = format_ident!("To{}", base_name);
let to_trait_fn_name = format_ident!("to_{}", base_name.to_string().to_case(Case::Snake));
let mut enum_variants = Vec::new();
let mut struct_defs = Vec::new();
let mut ref_variants = Vec::new();
let mut mut_variants = Vec::new();
let mut as_ref_arms = Vec::new();
let mut as_mut_arms = Vec::new();
let mut span_arms = Vec::new();
let mut from_impls = Vec::new();
let mut to_trait_impls = Vec::new();
for variant in parsed_input.variants {
match variant {
VariantInput::Unit(name) => {
let inner_type = name.clone();
struct_defs.push(quote! {
#[derive(Debug)]
pub struct #name {
pub span: rnix::TextRange,
}
});
enum_variants.push(quote! { #name(#inner_type) });
ref_variants.push(quote! { #name(&'a #inner_type) });
mut_variants.push(quote! { #name(&'a mut #inner_type) });
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
span_arms.push(quote! { Self::#name(inner) => inner.span });
from_impls.push(quote! {
impl From<#inner_type> for #base_name {
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
}
});
to_trait_impls.push(quote! {
impl #to_trait_name for #name {
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
}
});
}
VariantInput::Tuple(name, ty) => {
let field_name = format_ident!("inner");
struct_defs.push(quote! {
#[derive(Debug)]
pub struct #name {
pub #field_name: #ty,
pub span: rnix::TextRange,
}
});
let inner_type = name.clone();
enum_variants.push(quote! { #name(#inner_type) });
ref_variants.push(quote! { #name(&'a #inner_type) });
mut_variants.push(quote! { #name(&'a mut #inner_type) });
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
span_arms.push(quote! { Self::#name(inner) => inner.span });
from_impls.push(quote! {
impl From<#inner_type> for #base_name {
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
}
});
to_trait_impls.push(quote! {
impl #to_trait_name for #name {
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
}
});
}
VariantInput::Struct(name, mut fields) => {
let inner_type = name.clone();
fields.named.push(syn::Field {
attrs: vec![],
vis: syn::Visibility::Public(syn::token::Pub::default()),
mutability: syn::FieldMutability::None,
ident: Some(format_ident!("span")),
colon_token: Some(syn::token::Colon::default()),
ty: syn::parse_quote!(rnix::TextRange),
});
struct_defs.push(quote! {
#[derive(Debug)]
pub struct #name #fields
});
enum_variants.push(quote! { #name(#inner_type) });
ref_variants.push(quote! { #name(&'a #inner_type) });
mut_variants.push(quote! { #name(&'a mut #inner_type) });
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
span_arms.push(quote! { Self::#name(inner) => inner.span });
from_impls.push(quote! {
impl From<#inner_type> for #base_name {
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
}
});
to_trait_impls.push(quote! {
impl #to_trait_name for #name {
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
}
});
}
}
}
// Assemble the final generated code.
let expanded = quote! {
/// The main IR enum, generated by the `ir!` macro.
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
pub enum #base_name {
#( #enum_variants ),*
}
// The struct definitions for the enum variants.
#( #struct_defs )*
/// An immutable reference version of the IR enum.
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
pub enum #ref_name<'a> {
#( #ref_variants ),*
}
/// A mutable reference version of the IR enum.
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
pub enum #mut_name<'a> {
#( #mut_variants ),*
}
impl #base_name {
/// Converts a `&Ir` into a `IrRef`.
pub fn as_ref(&self) -> #ref_name<'_> {
match self {
#( #as_ref_arms ),*
}
}
/// Converts a `&mut Ir` into a `IrMut`.
pub fn as_mut(&mut self) -> #mut_name<'_> {
match self {
#( #as_mut_arms ),*
}
}
pub fn span(&self) -> rnix::TextRange {
match self {
#( #span_arms ),*
}
}
}
// `From` implementations for converting variant structs into the main enum.
#( #from_impls )*
/// A trait for converting a variant struct into the main IR enum.
pub trait #to_trait_name {
/// Performs the conversion.
fn #to_trait_fn_name(self) -> #base_name;
}
// Implement the `ToIr` trait for each variant struct.
#( #to_trait_impls )*
};
TokenStream::from(expanded)
}

View File

@@ -1,13 +0,0 @@
//! This crate provides procedural macros for the nix-js project.
use proc_macro::TokenStream;
mod ir;
/// A procedural macro to reduce boilerplate when defining an Intermediate Representation (IR).
///
/// It generates an enum for the IR, along with `Ref` and `Mut` variants,
/// `From` implementations, and a `ToIr` trait.
#[proc_macro]
pub fn ir(input: TokenStream) -> TokenStream {
ir::ir_impl(input)
}

View File

@@ -2,7 +2,6 @@
name = "nix-js" name = "nix-js"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
build = "build.rs"
[dependencies] [dependencies]
mimalloc = "0.1" mimalloc = "0.1"
@@ -27,6 +26,7 @@ miette = { version = "7.4", features = ["fancy"] }
hashbrown = "0.16" hashbrown = "0.16"
string-interner = "0.19" string-interner = "0.19"
bumpalo = { version = "3.20", features = ["allocator-api2", "boxed", "collections"] }
rust-embed="8.11" rust-embed="8.11"
@@ -53,7 +53,7 @@ bzip2 = "0.6"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
# spec 1.0.0 # spec 1.0.0
toml = "0.9.9" toml = "=0.9.9"
dirs = "6.0" dirs = "6.0"
tempfile = "3.24" tempfile = "3.24"
rusqlite = { version = "0.38", features = ["bundled"] } rusqlite = { version = "0.38", features = ["bundled"] }
@@ -61,7 +61,6 @@ rusqlite = { version = "0.38", features = ["bundled"] }
rnix = "0.14" rnix = "0.14"
rowan = "0.16" rowan = "0.16"
nix-js-macros = { path = "../nix-js-macros" }
ere = "0.2.4" ere = "0.2.4"
num_enum = "0.7.5" num_enum = "0.7.5"
tap = "1.0.1" tap = "1.0.1"
@@ -74,12 +73,20 @@ http-body-util = { version = "0.1", optional = true }
http = { version = "1", optional = true } http = { version = "1", optional = true }
uuid = { version = "1", features = ["v4"], optional = true } uuid = { version = "1", features = ["v4"], optional = true }
ghost-cell = "0.2"
colored = "3.1"
boxing = "0.1"
gc-arena = { version = "0.5.3", features = ["allocator-api2"] }
allocator-api2 = "0.4.0"
smallvec = "1.15.1"
[features] [features]
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"] inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]
prof = [] prof = []
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] } criterion = { version = "0.8", features = ["html_reports"] }
test-log = { version = "0.2", features = ["trace"] }
[[bench]] [[bench]]
name = "basic_ops" name = "basic_ops"

View File

@@ -1,7 +1,8 @@
mod utils; mod utils;
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box; use std::hint::black_box;
use criterion::{Criterion, criterion_group, criterion_main};
use utils::eval; use utils::eval;
fn bench_arithmetic(c: &mut Criterion) { fn bench_arithmetic(c: &mut Criterion) {

View File

@@ -1,7 +1,8 @@
mod utils; mod utils;
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box; use std::hint::black_box;
use criterion::{Criterion, criterion_group, criterion_main};
use utils::eval; use utils::eval;
fn bench_builtin_math(c: &mut Criterion) { fn bench_builtin_math(c: &mut Criterion) {

View File

@@ -1,8 +1,9 @@
mod utils; mod utils;
use std::hint::black_box;
use criterion::{Criterion, criterion_group, criterion_main}; use criterion::{Criterion, criterion_group, criterion_main};
use nix_js::context::Context; use nix_js::context::Context;
use std::hint::black_box;
use utils::compile; use utils::compile;
fn bench_parse_and_downgrade(c: &mut Criterion) { fn bench_parse_and_downgrade(c: &mut Criterion) {

View File

@@ -1,7 +1,8 @@
mod utils; mod utils;
use criterion::{Criterion, criterion_group, criterion_main};
use std::hint::black_box; use std::hint::black_box;
use criterion::{Criterion, criterion_group, criterion_main};
use utils::eval; use utils::eval;
fn bench_non_recursive(c: &mut Criterion) { fn bench_non_recursive(c: &mut Criterion) {

View File

@@ -1,70 +0,0 @@
use std::path::Path;
use std::process::Command;
fn main() {
let runtime_ts_dir = Path::new("runtime-ts");
let dist_runtime = runtime_ts_dir.join("dist/runtime.js");
if !runtime_ts_dir.exists() {
println!("cargo::warning=runtime-ts directory not found, using existing runtime.js");
return;
}
println!("cargo::rerun-if-changed=runtime-ts/src");
println!("cargo::rerun-if-changed=runtime-ts/package.json");
println!("cargo::rerun-if-changed=runtime-ts/tsconfig.json");
println!("cargo::rerun-if-changed=runtime-ts/build.mjs");
if !runtime_ts_dir.join("node_modules").exists() {
println!("Installing npm dependencies...");
let npm_cmd = if cfg!(target_os = "windows") {
"npm.cmd"
} else {
"npm"
};
let status = Command::new(npm_cmd)
.arg("install")
.current_dir(runtime_ts_dir)
.status()
.expect("Failed to run npm install. Is Node.js installed?");
if !status.success() {
panic!("npm install failed. Please check your Node.js installation.");
}
}
println!("Running TypeScript type checking...");
let npm_cmd = if cfg!(target_os = "windows") {
"npm.cmd"
} else {
"npm"
};
let status = Command::new(npm_cmd)
.arg("run")
.arg("typecheck")
.current_dir(runtime_ts_dir)
.status()
.expect("Failed to run type checking");
if !status.success() {
panic!("TypeScript type checking failed! Fix type errors before building.");
}
println!("Building runtime.js from TypeScript...");
let status = Command::new(npm_cmd)
.arg("run")
.arg("build")
.current_dir(runtime_ts_dir)
.status()
.expect("Failed to build runtime");
if !status.success() {
panic!("Runtime build failed!");
}
if dist_runtime.exists() {
println!("Successfully built runtime.js",);
} else {
panic!("dist/runtime.js not found after build");
}
}

View File

@@ -8,16 +8,15 @@
"name": "nix-js-runtime", "name": "nix-js-runtime",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@eslint/json": "^1.0.1",
"eslint": "^9.39.2",
"globals": "^17.3.0", "globals": "^17.3.0",
"jiti": "^2.6.1", "js-sdsl": "^4.4.2"
"js-sdsl": "^4.4.2",
"typescript-eslint": "^8.55.0"
}, },
"devDependencies": { "devDependencies": {
"esbuild": "^0.24.2", "esbuild": "^0.24.2",
"typescript": "^5.7.2" "eslint": "^9.39.2",
"jiti": "^2.6.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.55.0"
} }
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
@@ -449,6 +448,7 @@
"version": "4.9.1", "version": "4.9.1",
"resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
@@ -467,6 +467,7 @@
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -479,6 +480,7 @@
"version": "4.12.2", "version": "4.12.2",
"resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0" "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@@ -488,6 +490,7 @@
"version": "0.21.1", "version": "0.21.1",
"resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.1.tgz", "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.1.tgz",
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/object-schema": "^2.1.7", "@eslint/object-schema": "^2.1.7",
@@ -502,6 +505,7 @@
"version": "0.4.2", "version": "0.4.2",
"resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.17.0" "@eslint/core": "^0.17.0"
@@ -514,6 +518,7 @@
"version": "0.17.0", "version": "0.17.0",
"resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz", "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.15" "@types/json-schema": "^7.0.15"
@@ -522,22 +527,11 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/core": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@eslint/core/-/core-1.1.0.tgz",
"integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
@@ -561,6 +555,7 @@
"version": "14.0.0", "version": "14.0.0",
"resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -573,6 +568,7 @@
"version": "9.39.2", "version": "9.39.2",
"resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.2.tgz", "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.39.2.tgz",
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -581,47 +577,21 @@
"url": "https://eslint.org/donate" "url": "https://eslint.org/donate"
} }
}, },
"node_modules/@eslint/json": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@eslint/json/-/json-1.0.1.tgz",
"integrity": "sha512-bE2nGv8/U+uRvQEJWOgCsZCa65XsCBgxyyx/sXtTHVv0kqdauACLzyp7A1C3yNn7pRaWjIt5acxY+TAbSyIJXw==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^1.1.0",
"@eslint/plugin-kit": "^0.6.0",
"@humanwhocodes/momoa": "^3.3.10",
"natural-compare": "^1.4.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/object-schema": { "node_modules/@eslint/object-schema": {
"version": "2.1.7", "version": "2.1.7",
"resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz", "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz",
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/plugin-kit": {
"version": "0.6.0",
"resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz",
"integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^1.1.0",
"levn": "^0.4.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz",
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=18.18.0" "node": ">=18.18.0"
@@ -631,6 +601,7 @@
"version": "0.16.7", "version": "0.16.7",
"resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz", "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz",
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@humanfs/core": "^0.19.1", "@humanfs/core": "^0.19.1",
@@ -644,6 +615,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=12.22" "node": ">=12.22"
@@ -653,19 +625,11 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@humanwhocodes/momoa": {
"version": "3.3.10",
"resolved": "https://registry.npmmirror.com/@humanwhocodes/momoa/-/momoa-3.3.10.tgz",
"integrity": "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@humanwhocodes/retry": { "node_modules/@humanwhocodes/retry": {
"version": "0.4.3", "version": "0.4.3",
"resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz", "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz",
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=18.18" "node": ">=18.18"
@@ -679,18 +643,21 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
"integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.12.2", "@eslint-community/regexpp": "^4.12.2",
@@ -719,6 +686,7 @@
"version": "7.0.5", "version": "7.0.5",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 4" "node": ">= 4"
@@ -728,6 +696,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.55.0.tgz",
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@@ -753,6 +722,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
"integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.55.0", "@typescript-eslint/tsconfig-utils": "^8.55.0",
@@ -774,6 +744,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
"integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.55.0", "@typescript-eslint/types": "8.55.0",
@@ -791,6 +762,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
"integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -807,6 +779,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
"integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.55.0", "@typescript-eslint/types": "8.55.0",
@@ -831,6 +804,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.55.0.tgz",
"integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -844,6 +818,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
"integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.55.0", "@typescript-eslint/project-service": "8.55.0",
@@ -871,6 +846,7 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
@@ -880,6 +856,7 @@
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
@@ -895,6 +872,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.55.0.tgz",
"integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.9.1", "@eslint-community/eslint-utils": "^4.9.1",
@@ -918,6 +896,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
"integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.55.0", "@typescript-eslint/types": "8.55.0",
@@ -935,6 +914,7 @@
"version": "8.15.0", "version": "8.15.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"bin": { "bin": {
@@ -948,6 +928,7 @@
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -957,6 +938,7 @@
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@@ -973,6 +955,7 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
@@ -988,18 +971,21 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
@@ -1010,6 +996,7 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -1019,6 +1006,7 @@
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -1035,6 +1023,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
@@ -1047,18 +1036,21 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
@@ -1073,6 +1065,7 @@
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -1090,6 +1083,7 @@
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
@@ -1137,6 +1131,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -1149,6 +1144,7 @@
"version": "9.39.2", "version": "9.39.2",
"resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.39.2.tgz", "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.39.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@@ -1209,6 +1205,7 @@
"version": "8.4.0", "version": "8.4.0",
"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz", "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz",
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
@@ -1225,6 +1222,7 @@
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1237,6 +1235,7 @@
"version": "0.17.0", "version": "0.17.0",
"resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz", "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.15" "@types/json-schema": "^7.0.15"
@@ -1249,6 +1248,7 @@
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.17.0", "@eslint/core": "^0.17.0",
@@ -1262,6 +1262,7 @@
"version": "10.4.0", "version": "10.4.0",
"resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz", "resolved": "https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz",
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"acorn": "^8.15.0", "acorn": "^8.15.0",
@@ -1279,6 +1280,7 @@
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz", "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz",
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"estraverse": "^5.1.0" "estraverse": "^5.1.0"
@@ -1291,6 +1293,7 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
@@ -1303,6 +1306,7 @@
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
@@ -1312,6 +1316,7 @@
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -1321,24 +1326,28 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-json-stable-stringify": { "node_modules/fast-json-stable-stringify": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-levenshtein": { "node_modules/fast-levenshtein": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fdir": { "node_modules/fdir": {
"version": "6.5.0", "version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.0.0"
@@ -1356,6 +1365,7 @@
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"flat-cache": "^4.0.0" "flat-cache": "^4.0.0"
@@ -1368,6 +1378,7 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"locate-path": "^6.0.0", "locate-path": "^6.0.0",
@@ -1384,6 +1395,7 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz",
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"flatted": "^3.2.9", "flatted": "^3.2.9",
@@ -1397,12 +1409,14 @@
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz", "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
@@ -1427,6 +1441,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -1436,6 +1451,7 @@
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 4" "node": ">= 4"
@@ -1445,6 +1461,7 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"parent-module": "^1.0.0", "parent-module": "^1.0.0",
@@ -1461,6 +1478,7 @@
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.8.19" "node": ">=0.8.19"
@@ -1470,6 +1488,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -1479,6 +1498,7 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
@@ -1491,12 +1511,14 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.6.1", "version": "2.6.1",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz", "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"bin": { "bin": {
@@ -1517,6 +1539,7 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -1529,24 +1552,28 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-stable-stringify-without-jsonify": { "node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"json-buffer": "3.0.1" "json-buffer": "3.0.1"
@@ -1556,6 +1583,7 @@
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"prelude-ls": "^1.2.1", "prelude-ls": "^1.2.1",
@@ -1569,6 +1597,7 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"p-locate": "^5.0.0" "p-locate": "^5.0.0"
@@ -1584,12 +1613,14 @@
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
@@ -1602,18 +1633,21 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/natural-compare": { "node_modules/natural-compare": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"deep-is": "^0.1.3", "deep-is": "^0.1.3",
@@ -1631,6 +1665,7 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"yocto-queue": "^0.1.0" "yocto-queue": "^0.1.0"
@@ -1646,6 +1681,7 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"p-limit": "^3.0.2" "p-limit": "^3.0.2"
@@ -1661,6 +1697,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"callsites": "^3.0.0" "callsites": "^3.0.0"
@@ -1673,6 +1710,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -1682,6 +1720,7 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -1691,6 +1730,7 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"engines": { "engines": {
@@ -1704,6 +1744,7 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
@@ -1713,6 +1754,7 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -1722,6 +1764,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -1731,6 +1774,7 @@
"version": "7.7.4", "version": "7.7.4",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -1743,6 +1787,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
@@ -1755,6 +1800,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -1764,6 +1810,7 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -1776,6 +1823,7 @@
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@@ -1788,6 +1836,7 @@
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -1804,6 +1853,7 @@
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz", "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
"integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.12" "node": ">=18.12"
@@ -1816,6 +1866,7 @@
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"prelude-ls": "^1.2.1" "prelude-ls": "^1.2.1"
@@ -1828,6 +1879,7 @@
"version": "5.9.3", "version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true, "peer": true,
"bin": { "bin": {
@@ -1842,6 +1894,7 @@
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.55.0.tgz", "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.55.0.tgz",
"integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.55.0", "@typescript-eslint/eslint-plugin": "8.55.0",
@@ -1865,6 +1918,7 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"punycode": "^2.1.0" "punycode": "^2.1.0"
@@ -1874,6 +1928,7 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
@@ -1889,6 +1944,7 @@
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -1898,6 +1954,7 @@
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"

View File

@@ -10,12 +10,13 @@
}, },
"devDependencies": { "devDependencies": {
"esbuild": "^0.24.2", "esbuild": "^0.24.2",
"typescript": "^5.7.2" "eslint": "^9.39.2",
"typescript": "^5.7.2",
"typescript-eslint": "^8.55.0",
"jiti": "^2.6.1"
}, },
"dependencies": { "dependencies": {
"eslint": "^9.39.2",
"globals": "^17.3.0", "globals": "^17.3.0",
"js-sdsl": "^4.4.2", "js-sdsl": "^4.4.2"
"typescript-eslint": "^8.55.0"
} }
} }

View File

@@ -289,13 +289,14 @@ export const toStringFunc = (value: NixValue): NixString => {
return coerceToStringWithContext(value, StringCoercionMode.ToString, false); return coerceToStringWithContext(value, StringCoercionMode.ToString, false);
}; };
export type JsonValue = number | boolean | string | null | { [key: string]: JsonValue } | Array<JsonValue>;
export const nixValueToJson = ( export const nixValueToJson = (
value: NixValue, value: NixValue,
strict: boolean, strict: boolean,
outContext: NixStringContext, outContext: NixStringContext,
copyToStore: boolean, copyToStore: boolean,
seen: Set<NixValue> = new Set(), seen: Set<NixValue> = new Set(),
): unknown => { ): JsonValue => {
const v = strict ? force(value) : value; const v = strict ? force(value) : value;
if (isThunk(v) || typeof v === "function") if (isThunk(v) || typeof v === "function")
@@ -358,7 +359,7 @@ export const nixValueToJson = (
return nixValueToJson(v.get("outPath") as NixValue, strict, outContext, copyToStore, seen); return nixValueToJson(v.get("outPath") as NixValue, strict, outContext, copyToStore, seen);
} }
const result: Record<string, unknown> = {}; const result: { [key: string]: JsonValue } = {};
const keys = Array.from(v.keys()).sort(); const keys = Array.from(v.keys()).sort();
for (const key of keys) { for (const key of keys) {
result[key] = nixValueToJson(v.get(key) as NixValue, strict, outContext, copyToStore, seen); result[key] = nixValueToJson(v.get(key) as NixValue, strict, outContext, copyToStore, seen);

View File

@@ -7,7 +7,7 @@ import {
import { force } from "../thunk"; import { force } from "../thunk";
import { forceAttrs, forceList, forceStringNoCtx, forceStringValue } from "../type-assert"; import { forceAttrs, forceList, forceStringNoCtx, forceStringValue } from "../type-assert";
import type { NixAttrs, NixValue } from "../types"; import type { NixAttrs, NixValue } from "../types";
import { coerceToString, nixValueToJson, StringCoercionMode } from "./conversion"; import { coerceToString, type JsonValue, nixValueToJson, StringCoercionMode } from "./conversion";
export interface OutputInfo { export interface OutputInfo {
path: string; path: string;
@@ -205,9 +205,9 @@ const structuredAttrsExcludedKeys = new Set([
const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]); const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]);
const sortedJsonStringify = (obj: Record<string, unknown>): string => { const sortedJsonStringify = (obj: Record<string, JsonValue>): string => {
const sortedKeys = Object.keys(obj).sort(); const sortedKeys = Object.keys(obj).sort();
const sortedObj: Record<string, unknown> = {}; const sortedObj: Record<string, JsonValue> = {};
for (const key of sortedKeys) { for (const key of sortedKeys) {
sortedObj[key] = obj[key]; sortedObj[key] = obj[key];
} }
@@ -224,14 +224,14 @@ const extractEnv = (
const env = new Map<string, string>(); const env = new Map<string, string>();
if (structuredAttrs) { if (structuredAttrs) {
const jsonAttrs: Record<string, unknown> = {}; const jsonAttrs: Record<string, JsonValue> = {};
for (const [key, value] of attrs) { for (const [key, value] of attrs) {
if (!structuredAttrsExcludedKeys.has(key)) { if (!structuredAttrsExcludedKeys.has(key)) {
const forcedValue = force(value as NixValue); const forcedValue = force(value);
if (ignoreNulls && forcedValue === null) { if (ignoreNulls && forcedValue === null) {
continue; continue;
} }
jsonAttrs[key] = nixValueToJson(value as NixValue, true, outContext, true); jsonAttrs[key] = nixValueToJson(value, true, outContext, true);
} }
if (key === "allowedReferences") { if (key === "allowedReferences") {

View File

@@ -27,7 +27,7 @@ export const deepSeq =
recurse(val); recurse(val);
} }
} else if (isAttrs(forced)) { } else if (isAttrs(forced)) {
for (const [_, val] of Object.entries(forced)) { for (const [_, val] of forced.entries()) {
recurse(val); recurse(val);
} }
} }

View File

@@ -1,5 +1,5 @@
import { createThunk, force } from "../thunk"; import { createThunk, force } from "../thunk";
import type { NixAttrs, NixValue } from "../types"; import type { NixAttrs, NixFunction, NixValue } from "../types";
import * as arithmetic from "./arithmetic"; import * as arithmetic from "./arithmetic";
import * as attrs from "./attrs"; import * as attrs from "./attrs";
import * as conversion from "./conversion"; import * as conversion from "./conversion";
@@ -24,31 +24,31 @@ export interface PrimopMetadata {
} }
export const mkPrimop = ( export const mkPrimop = (
func: (...args: NixValue[]) => NixValue, func: NixFunction,
name: string, name: string,
arity: number, arity: number,
applied: number = 0, applied: number = 0,
): ((...args: NixValue[]) => NixValue) => { ): ((...args: NixValue[]) => NixValue) => {
(func as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = { func[PRIMOP_METADATA] = {
name, name,
arity, arity,
applied, applied,
} satisfies PrimopMetadata; } satisfies PrimopMetadata;
if (applied < arity - 1) { if (applied < arity - 1) {
const wrappedFunc = ((...args: NixValue[]) => { const wrappedFunc: NixFunction = ((arg: NixValue) => {
const result = func(...args); const result = func(arg);
if (typeof result === "function") { if (typeof result === "function") {
return mkPrimop(result, name, arity, applied + args.length); return mkPrimop(result, name, arity, applied + 1);
} }
return result; return result;
}) as (...args: NixValue[]) => NixValue; });
(wrappedFunc as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = { wrappedFunc[PRIMOP_METADATA] = {
name, name,
arity, arity,
applied, applied,
} satisfies PrimopMetadata; };
return wrappedFunc; return wrappedFunc;
} }
@@ -57,8 +57,8 @@ export const mkPrimop = (
}; };
export const isPrimop = ( export const isPrimop = (
value: unknown, value: NixValue,
): value is ((...args: never[]) => unknown) & { [PRIMOP_METADATA]: PrimopMetadata } => { ): value is NixFunction & { [PRIMOP_METADATA]: PrimopMetadata } => {
return ( return (
typeof value === "function" && typeof value === "function" &&
PRIMOP_METADATA in value && PRIMOP_METADATA in value &&
@@ -67,7 +67,7 @@ export const isPrimop = (
); );
}; };
export const getPrimopMetadata = (func: unknown): PrimopMetadata | undefined => { export const getPrimopMetadata = (func: NixValue): PrimopMetadata | undefined => {
if (isPrimop(func)) { if (isPrimop(func)) {
return func[PRIMOP_METADATA]; return func[PRIMOP_METADATA];
} }

View File

@@ -16,6 +16,7 @@ import { CatchableError, isNixPath, NixPath } from "../types";
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion"; import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
import { baseNameOf } from "./path"; import { baseNameOf } from "./path";
import { isAttrs, isPath, isString } from "./type-check"; import { isAttrs, isPath, isString } from "./type-check";
import { execBytecode, execBytecodeScoped } from "../vm";
const importCache = new Map<string, NixValue>(); const importCache = new Map<string, NixValue>();
@@ -49,7 +50,8 @@ export const importFunc = (path: NixValue): NixValue => {
return cached; return cached;
} }
const result = Deno.core.ops.op_import(pathStr); const [code, currentDir] = Deno.core.ops.op_import(pathStr);
const result = execBytecode(code, currentDir);
importCache.set(pathStr, result); importCache.set(pathStr, result);
return result; return result;
@@ -63,10 +65,8 @@ export const scopedImport =
const pathStr = realisePath(path); const pathStr = realisePath(path);
const code = Deno.core.ops.op_scoped_import(pathStr, scopeKeys); const [code, currentDir] = Deno.core.ops.op_scoped_import(pathStr, scopeKeys);
return execBytecodeScoped(code, currentDir, scopeAttrs);
const scopedFunc = Function(`return (${code})`)();
return scopedFunc(scopeAttrs);
}; };
export const storePath = (pathArg: NixValue): StringWithContext => { export const storePath = (pathArg: NixValue): StringWithContext => {
@@ -185,7 +185,7 @@ export const fetchGit = (args: NixValue): NixAttrs => {
]); ]);
} }
const attrs = forceAttrs(args); const attrs = forceAttrs(args);
const url = forceStringValue(select("attrs", ["url"])); const url = forceStringValue(select(attrs, ["url"]));
const gitRef = attrs.has("ref") ? forceStringValue(attrs.get("ref") as NixValue) : null; const gitRef = attrs.has("ref") ? forceStringValue(attrs.get("ref") as NixValue) : null;
const rev = attrs.has("rev") ? forceStringValue(attrs.get("rev") as NixValue) : null; const rev = attrs.has("rev") ? forceStringValue(attrs.get("rev") as NixValue) : null;
const shallow = attrs.has("shallow") ? forceBool(attrs.get("shallow") as NixValue) : false; const shallow = attrs.has("shallow") ? forceBool(attrs.get("shallow") as NixValue) : false;

View File

@@ -318,10 +318,12 @@ export const splitVersion = (s: NixValue): NixValue => {
return components; return components;
}; };
export const traceVerbose = (_e1: NixValue, e2: NixValue): NixStrictValue => { export const traceVerbose =
// TODO: implement traceVerbose (_e1: NixValue) =>
return force(e2); (e2: NixValue): NixStrictValue => {
}; // TODO: implement traceVerbose
return force(e2);
};
export const tryEval = (e: NixValue): NixAttrs => { export const tryEval = (e: NixValue): NixAttrs => {
try { try {

View File

@@ -21,6 +21,7 @@ import { HAS_CONTEXT } from "./string-context";
import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk"; import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk";
import { forceBool } from "./type-assert"; import { forceBool } from "./type-assert";
import { IS_PATH, mkAttrs, mkFunction, type NixValue } from "./types"; import { IS_PATH, mkAttrs, mkFunction, type NixValue } from "./types";
import { execBytecode, execBytecodeScoped, vmStrings, vmConstants } from "./vm";
export type NixRuntime = typeof Nix; export type NixRuntime = typeof Nix;
@@ -55,6 +56,11 @@ export const Nix = {
op, op,
builtins, builtins,
strings: vmStrings,
constants: vmConstants,
execBytecode,
execBytecodeScoped,
replBindings, replBindings,
setReplBinding: (name: string, value: NixValue) => { setReplBinding: (name: string, value: NixValue) => {
replBindings.set(name, value); replBindings.set(name, value);
@@ -89,3 +95,4 @@ globalThis.$og = op.gt;
globalThis.$oc = op.concat; globalThis.$oc = op.concat;
globalThis.$ou = op.update; globalThis.$ou = op.update;
globalThis.$b = builtins; globalThis.$b = builtins;
globalThis.$e = new Map();

View File

@@ -270,6 +270,12 @@ export const op = {
update: (a: NixValue, b: NixValue): NixAttrs => { update: (a: NixValue, b: NixValue): NixAttrs => {
const mapA = forceAttrs(a); const mapA = forceAttrs(a);
const mapB = forceAttrs(b); const mapB = forceAttrs(b);
if (mapA.size === 0) {
return mapB;
}
if (mapB.size === 0) {
return mapA;
}
const result: NixAttrs = new Map(mapA); const result: NixAttrs = new Map(mapA);
for (const [k, v] of mapB) { for (const [k, v] of mapB) {
result.set(k, v); result.set(k, v);

View File

@@ -39,36 +39,40 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
return "<LAMBDA>"; return "<LAMBDA>";
} }
if (typeof value === "object") { if (IS_CYCLE in value) {
if (IS_CYCLE in value) { return "«repeated»";
return "«repeated»";
}
if (seen.has(value)) {
return "«repeated»";
}
seen.add(value);
if (isNixPath(value)) {
return value.value;
}
if (isStringWithContext(value)) {
return printString(value.value);
}
if (Array.isArray(value)) {
const items = value.map((v) => printValue(v, seen)).join(" ");
return `[ ${items} ]`;
}
const entries = Object.entries(value)
.map(([k, v]) => `${printSymbol(k)} = ${printValue(v, seen)};`)
.join(" ");
return `{${entries ? ` ${entries} ` : " "}}`;
} }
throw new Error("unreachable"); if (isNixPath(value)) {
return value.value;
}
if (isStringWithContext(value)) {
return printString(value.value);
}
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(" ");
return `{${entries ? ` ${entries} ` : " "}}`;
}; };
const printString = (s: string): string => { const printString = (s: string): string => {

View File

@@ -132,12 +132,9 @@ export const forceDeep = (value: NixValue, seen: WeakSet<object> = new WeakSet()
} }
if (seen.has(forced)) { if (seen.has(forced)) {
if (Array.isArray(forced)) {
return [CYCLE_MARKER];
}
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); seen.add(forced);
} }

View File

@@ -1,3 +1,4 @@
import { PRIMOP_METADATA, type PrimopMetadata } from "./builtins";
import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context"; import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context";
import { type CYCLE_MARKER, force, type NixThunk } from "./thunk"; import { type CYCLE_MARKER, force, type NixThunk } from "./thunk";
import { forceAttrs, forceStringNoCtx } from "./type-assert"; import { forceAttrs, forceStringNoCtx } from "./type-assert";
@@ -28,7 +29,10 @@ export type NixNull = null;
export const ATTR_POSITIONS = Symbol("attrPositions"); export const ATTR_POSITIONS = Symbol("attrPositions");
export type NixList = NixValue[]; export type NixList = NixValue[];
export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, number> }; export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, number> };
export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs }; export type NixFunction = ((arg: NixValue) => NixValue) & {
args?: NixArgs;
[PRIMOP_METADATA]?: PrimopMetadata;
};
export class NixArgs { export class NixArgs {
required: string[]; required: string[];
optional: string[]; optional: string[];

View File

@@ -15,7 +15,7 @@ import type {
import type { op } from "../operators"; import type { op } from "../operators";
import type { createThunk, force } from "../thunk"; import type { createThunk, force } from "../thunk";
import type { forceBool } from "../type-assert"; import type { forceBool } from "../type-assert";
import type { mkAttrs, mkFunction, NixAttrs } from "../types"; import type { mkAttrs, mkFunction, NixAttrs, NixStrictValue } from "../types";
declare global { declare global {
var Nix: NixRuntime; var Nix: NixRuntime;
@@ -43,13 +43,14 @@ declare global {
var $oc: typeof op.concat; var $oc: typeof op.concat;
var $ou: typeof op.update; var $ou: typeof op.update;
var $b: typeof builtins; var $b: typeof builtins;
var $e: NixAttrs;
var $gb: typeof Nix.getReplBinding; var $gb: typeof Nix.getReplBinding;
namespace Deno { namespace Deno {
namespace core { namespace core {
namespace ops { namespace ops {
function op_import(path: string): NixValue; function op_import(path: string): [Uint8Array, string];
function op_scoped_import(path: string, scopeKeys: string[]): string; function op_scoped_import(path: string, scopeKeys: string[]): [Uint8Array, string];
function op_resolve_path(currentDir: string, path: string): string; function op_resolve_path(currentDir: string, path: string): string;
@@ -92,8 +93,8 @@ declare global {
function op_match(regex: string, text: string): (string | null)[] | null; function op_match(regex: string, text: string): (string | null)[] | null;
function op_split(regex: string, text: string): (string | (string | null)[])[]; function op_split(regex: string, text: string): (string | (string | null)[])[];
function op_from_json(json: string): unknown; function op_from_json(json: string): NixStrictValue;
function op_from_toml(toml: string): unknown; function op_from_toml(toml: string): NixStrictValue;
function op_to_xml(e: NixValue): [string, string[]]; function op_to_xml(e: NixValue): [string, string[]];
function op_finalize_derivation( function op_finalize_derivation(

617
nix-js/runtime-ts/src/vm.ts Normal file
View File

@@ -0,0 +1,617 @@
import {
assert,
call,
concatStringsWithContext,
hasAttr,
lookupWith,
mkPos,
resolvePath,
select,
selectWithDefault,
} from "./helpers";
import { op } from "./operators";
import { NixThunk } from "./thunk";
import { forceBool } from "./type-assert";
import { mkAttrs, NixArgs, type NixAttrs, type NixFunction, type NixValue } from "./types";
import { builtins } from "./builtins";
enum Op {
PushConst = 0x01,
PushString = 0x02,
PushNull = 0x03,
PushTrue = 0x04,
PushFalse = 0x05,
LoadLocal = 0x06,
LoadOuter = 0x07,
StoreLocal = 0x08,
AllocLocals = 0x09,
MakeThunk = 0x0A,
MakeClosure = 0x0B,
MakePatternClosure = 0x0C,
Call = 0x0D,
CallNoSpan = 0x0E,
MakeAttrs = 0x0F,
MakeAttrsDyn = 0x10,
MakeEmptyAttrs = 0x11,
Select = 0x12,
SelectDefault = 0x13,
HasAttr = 0x14,
MakeList = 0x15,
OpAdd = 0x16,
OpSub = 0x17,
OpMul = 0x18,
OpDiv = 0x19,
OpEq = 0x20,
OpNeq = 0x21,
OpLt = 0x22,
OpGt = 0x23,
OpLeq = 0x24,
OpGeq = 0x25,
OpConcat = 0x26,
OpUpdate = 0x27,
OpNeg = 0x28,
OpNot = 0x29,
ForceBool = 0x30,
JumpIfFalse = 0x31,
JumpIfTrue = 0x32,
Jump = 0x33,
ConcatStrings = 0x34,
ResolvePath = 0x35,
Assert = 0x36,
PushWith = 0x37,
PopWith = 0x38,
WithLookup = 0x39,
LoadBuiltins = 0x40,
LoadBuiltin = 0x41,
MkPos = 0x43,
LoadReplBinding = 0x44,
LoadScopedBinding = 0x45,
Return = 0x46,
}
interface ScopeChain {
locals: NixValue[];
parent: ScopeChain | null;
}
interface WithScope {
env: NixValue;
last: WithScope | null;
}
const strings: string[] = [];
const constants: NixValue[] = [];
const $e: NixAttrs = new Map();
function readU16(code: Uint8Array, offset: number): number {
return code[offset] | (code[offset + 1] << 8);
}
function readU32(code: Uint8Array, offset: number): number {
return (
code[offset] |
(code[offset + 1] << 8) |
(code[offset + 2] << 16) |
(code[offset + 3] << 24)
) >>> 0;
}
function readI32(code: Uint8Array, offset: number): number {
return code[offset] | (code[offset + 1] << 8) | (code[offset + 2] << 16) | (code[offset + 3] << 24);
}
export function execBytecode(code: Uint8Array, currentDir: string): NixValue {
const chain: ScopeChain = { locals: [], parent: null };
return execFrame(code, 0, chain, currentDir, null, null);
}
export function execBytecodeScoped(
code: Uint8Array,
currentDir: string,
scopeMap: NixAttrs,
): NixValue {
const chain: ScopeChain = { locals: [], parent: null };
return execFrame(code, 0, chain, currentDir, null, scopeMap);
}
function execFrame(
code: Uint8Array,
startPc: number,
chain: ScopeChain,
currentDir: string,
withScope: WithScope | null,
scopeMap: NixAttrs | null,
): NixValue {
const locals = chain.locals;
const stack: NixValue[] = [];
let pc = startPc;
for (;;) {
const opcode = code[pc++];
switch (opcode) {
case Op.PushConst: {
const idx = readU32(code, pc);
pc += 4;
stack.push(constants[idx]);
break;
}
case Op.PushString: {
const idx = readU32(code, pc);
pc += 4;
stack.push(strings[idx]);
break;
}
case Op.PushNull:
stack.push(null);
break;
case Op.PushTrue:
stack.push(true);
break;
case Op.PushFalse:
stack.push(false);
break;
case Op.LoadLocal: {
const idx = readU32(code, pc);
pc += 4;
stack.push(locals[idx]);
break;
}
case Op.LoadOuter: {
const layer = code[pc++];
const idx = readU32(code, pc);
pc += 4;
let c: ScopeChain = chain;
for (let i = 0; i < layer; i++) c = c.parent!;
stack.push(c.locals[idx]);
break;
}
case Op.StoreLocal: {
const idx = readU32(code, pc);
pc += 4;
locals[idx] = stack.pop()!;
break;
}
case Op.AllocLocals: {
const n = readU32(code, pc);
pc += 4;
for (let i = 0; i < n; i++) locals.push(null);
break;
}
case Op.MakeThunk: {
const bodyPc = readU32(code, pc);
pc += 4;
const labelIdx = readU32(code, pc);
pc += 4;
const label = strings[labelIdx];
const scopeChain = chain;
const scopeCode = code;
const scopeDir = currentDir;
const scopeWith = withScope;
stack.push(
new NixThunk(
() => execFrame(scopeCode, bodyPc, scopeChain, scopeDir, scopeWith, null),
label,
),
);
break;
}
case Op.MakeClosure: {
const bodyPc = readU32(code, pc);
pc += 4;
const nSlots = readU32(code, pc);
pc += 4;
const closureChain = chain;
const closureCode = code;
const closureDir = currentDir;
const closureWith = withScope;
const func: NixFunction = (arg: NixValue) => {
const innerLocals = new Array<NixValue>(1 + nSlots).fill(null);
innerLocals[0] = arg;
const innerChain: ScopeChain = { locals: innerLocals, parent: closureChain };
return execFrame(closureCode, bodyPc, innerChain, closureDir, closureWith, null);
};
stack.push(func);
break;
}
case Op.MakePatternClosure: {
const bodyPc = readU32(code, pc);
pc += 4;
const nSlots = readU32(code, pc);
pc += 4;
const nRequired = readU16(code, pc);
pc += 2;
const nOptional = readU16(code, pc);
pc += 2;
const hasEllipsis = code[pc++] !== 0;
const required: string[] = [];
for (let i = 0; i < nRequired; i++) {
required.push(strings[readU32(code, pc)]);
pc += 4;
}
const optional: string[] = [];
for (let i = 0; i < nOptional; i++) {
optional.push(strings[readU32(code, pc)]);
pc += 4;
}
const positions = new Map<string, number>();
const nTotal = nRequired + nOptional;
for (let i = 0; i < nTotal; i++) {
const nameIdx = readU32(code, pc);
pc += 4;
const spanId = readU32(code, pc);
pc += 4;
positions.set(strings[nameIdx], spanId);
}
const closureChain = chain;
const closureCode = code;
const closureDir = currentDir;
const closureWith = withScope;
const func: NixFunction = (arg: NixValue) => {
const innerLocals = new Array<NixValue>(1 + nSlots).fill(null);
innerLocals[0] = arg;
const innerChain: ScopeChain = { locals: innerLocals, parent: closureChain };
return execFrame(closureCode, bodyPc, innerChain, closureDir, closureWith, null);
};
func.args = new NixArgs(required, optional, positions, hasEllipsis);
stack.push(func);
break;
}
case Op.Call: {
const spanId = readU32(code, pc);
pc += 4;
const arg = stack.pop()!;
const func = stack.pop()!;
stack.push(call(func, arg, spanId));
break;
}
case Op.CallNoSpan: {
const arg = stack.pop()!;
const func = stack.pop()!;
stack.push(call(func, arg));
break;
}
case Op.MakeAttrs: {
const n = readU32(code, pc);
pc += 4;
const spanValues: number[] = [];
for (let i = 0; i < n; i++) {
spanValues.push(stack.pop() as number);
}
spanValues.reverse();
const map: NixAttrs = new Map();
const posMap = new Map<string, number>();
const pairs: [string, NixValue][] = [];
for (let i = 0; i < n; i++) {
const val = stack.pop()!;
const key = stack.pop() as string;
pairs.push([key, val]);
}
pairs.reverse();
for (let i = 0; i < n; i++) {
map.set(pairs[i][0], pairs[i][1]);
posMap.set(pairs[i][0], spanValues[i]);
}
stack.push(mkAttrs(map, posMap));
break;
}
case Op.MakeAttrsDyn: {
const nStatic = readU32(code, pc);
pc += 4;
const nDyn = readU32(code, pc);
pc += 4;
const dynTriples: [NixValue, NixValue, number][] = [];
for (let i = 0; i < nDyn; i++) {
const dynSpan = stack.pop() as number;
const dynVal = stack.pop()!;
const dynKey = stack.pop()!;
dynTriples.push([dynKey, dynVal, dynSpan]);
}
dynTriples.reverse();
const spanValues: number[] = [];
for (let i = 0; i < nStatic; i++) {
spanValues.push(stack.pop() as number);
}
spanValues.reverse();
const map: NixAttrs = new Map();
const posMap = new Map<string, number>();
const pairs: [string, NixValue][] = [];
for (let i = 0; i < nStatic; i++) {
const val = stack.pop()!;
const key = stack.pop() as string;
pairs.push([key, val]);
}
pairs.reverse();
for (let i = 0; i < nStatic; i++) {
map.set(pairs[i][0], pairs[i][1]);
posMap.set(pairs[i][0], spanValues[i]);
}
const dynKeys: NixValue[] = [];
const dynVals: NixValue[] = [];
const dynSpans: number[] = [];
for (const [k, v, s] of dynTriples) {
dynKeys.push(k);
dynVals.push(v);
dynSpans.push(s);
}
stack.push(mkAttrs(map, posMap, { dynKeys, dynVals, dynSpans }));
break;
}
case Op.MakeEmptyAttrs:
stack.push($e);
break;
case Op.Select: {
const nKeys = readU16(code, pc);
pc += 2;
const spanId = readU32(code, pc);
pc += 4;
const keys: NixValue[] = [];
for (let i = 0; i < nKeys; i++) keys.push(stack.pop()!);
keys.reverse();
const obj = stack.pop()!;
stack.push(select(obj, keys, spanId));
break;
}
case Op.SelectDefault: {
const nKeys = readU16(code, pc);
pc += 2;
const spanId = readU32(code, pc);
pc += 4;
const defaultVal = stack.pop()!;
const keys: NixValue[] = [];
for (let i = 0; i < nKeys; i++) keys.push(stack.pop()!);
keys.reverse();
const obj = stack.pop()!;
stack.push(selectWithDefault(obj, keys, defaultVal, spanId));
break;
}
case Op.HasAttr: {
const nKeys = readU16(code, pc);
pc += 2;
const keys: NixValue[] = [];
for (let i = 0; i < nKeys; i++) keys.push(stack.pop()!);
keys.reverse();
const obj = stack.pop()!;
stack.push(hasAttr(obj, keys));
break;
}
case Op.MakeList: {
const count = readU32(code, pc);
pc += 4;
const items: NixValue[] = new Array(count);
for (let i = count - 1; i >= 0; i--) {
items[i] = stack.pop()!;
}
stack.push(items);
break;
}
case Op.OpAdd: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.add(a, b));
break;
}
case Op.OpSub: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.sub(a, b));
break;
}
case Op.OpMul: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.mul(a, b));
break;
}
case Op.OpDiv: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.div(a, b));
break;
}
case Op.OpEq: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.eq(a, b));
break;
}
case Op.OpNeq: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(!op.eq(a, b));
break;
}
case Op.OpLt: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.lt(a, b));
break;
}
case Op.OpGt: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.gt(a, b));
break;
}
case Op.OpLeq: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(!op.gt(a, b));
break;
}
case Op.OpGeq: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(!op.lt(a, b));
break;
}
case Op.OpConcat: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.concat(a, b));
break;
}
case Op.OpUpdate: {
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(op.update(a, b));
break;
}
case Op.OpNeg: {
const a = stack.pop()!;
stack.push(op.sub(0n, a));
break;
}
case Op.OpNot: {
const a = stack.pop()!;
stack.push(!forceBool(a));
break;
}
case Op.ForceBool: {
const val = stack.pop()!;
stack.push(forceBool(val));
break;
}
case Op.JumpIfFalse: {
const offset = readI32(code, pc);
pc += 4;
const val = stack.pop()!;
if (val === false) {
pc += offset;
}
break;
}
case Op.JumpIfTrue: {
const offset = readI32(code, pc);
pc += 4;
const val = stack.pop()!;
if (val === true) {
pc += offset;
}
break;
}
case Op.Jump: {
const offset = readI32(code, pc);
pc += 4;
pc += offset;
break;
}
case Op.ConcatStrings: {
const nParts = readU16(code, pc);
pc += 2;
const forceString = code[pc++] !== 0;
const parts: NixValue[] = new Array(nParts);
for (let i = nParts - 1; i >= 0; i--) {
parts[i] = stack.pop()!;
}
stack.push(concatStringsWithContext(parts, forceString));
break;
}
case Op.ResolvePath: {
const pathExpr = stack.pop()!;
stack.push(resolvePath(currentDir, pathExpr));
break;
}
case Op.Assert: {
const rawIdx = readU32(code, pc);
pc += 4;
const spanId = readU32(code, pc);
pc += 4;
const expr = stack.pop()!;
const assertion = stack.pop()!;
stack.push(assert(assertion, expr, strings[rawIdx], spanId));
break;
}
case Op.PushWith: {
const namespace = stack.pop()!;
withScope = { env: namespace, last: withScope };
break;
}
case Op.PopWith:
withScope = withScope!.last;
break;
case Op.WithLookup: {
const nameIdx = readU32(code, pc);
pc += 4;
stack.push(lookupWith(strings[nameIdx], withScope!));
break;
}
case Op.LoadBuiltins:
stack.push(builtins);
break;
case Op.LoadBuiltin: {
const idx = readU32(code, pc);
pc += 4;
stack.push(builtins.get(strings[idx])!);
break;
}
case Op.MkPos: {
const spanId = readU32(code, pc);
pc += 4;
stack.push(mkPos(spanId));
break;
}
case Op.LoadReplBinding: {
const idx = readU32(code, pc);
pc += 4;
stack.push(Nix.getReplBinding(strings[idx]));
break;
}
case Op.LoadScopedBinding: {
const idx = readU32(code, pc);
pc += 4;
stack.push(scopeMap!.get(strings[idx])!);
break;
}
case Op.Return:
return stack.pop()!;
default:
throw new Error(`Unknown bytecode opcode: ${opcode ? `0x${opcode.toString(16)}` : "undefined"} at pc=${pc - 1}`);
}
}
}
declare const Nix: {
getReplBinding: (name: string) => NixValue;
};
export { strings as vmStrings, constants as vmConstants };

906
nix-js/src/bytecode.rs Normal file
View File

@@ -0,0 +1,906 @@
use std::ops::Deref;
use std::path::Path;
use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use rnix::TextRange;
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, SymId, ThunkId, UnOpKind};
#[derive(Clone, Hash, Eq, PartialEq)]
pub(crate) enum Constant {
Int(i64),
Float(u64),
}
pub struct Bytecode {
pub code: Box<[u8]>,
pub current_dir: String,
}
pub(crate) trait BytecodeContext {
fn intern_string(&mut self, s: &str) -> u32;
fn intern_constant(&mut self, c: Constant) -> u32;
fn register_span(&self, range: TextRange) -> u32;
fn get_sym(&self, id: SymId) -> &str;
fn get_current_dir(&self) -> &Path;
}
#[repr(u8)]
#[derive(Clone, Copy, TryFromPrimitive)]
#[allow(clippy::enum_variant_names)]
pub enum Op {
PushConst = 0x01,
PushString = 0x02,
PushNull = 0x03,
PushTrue = 0x04,
PushFalse = 0x05,
LoadLocal = 0x06,
LoadOuter = 0x07,
StoreLocal = 0x08,
AllocLocals = 0x09,
MakeThunk = 0x0A,
MakeClosure = 0x0B,
MakePatternClosure = 0x0C,
Call = 0x0D,
CallNoSpan = 0x0E,
MakeAttrs = 0x0F,
MakeAttrsDyn = 0x10,
MakeEmptyAttrs = 0x11,
Select = 0x12,
SelectDefault = 0x13,
HasAttr = 0x14,
MakeList = 0x15,
OpAdd = 0x16,
OpSub = 0x17,
OpMul = 0x18,
OpDiv = 0x19,
OpEq = 0x20,
OpNeq = 0x21,
OpLt = 0x22,
OpGt = 0x23,
OpLeq = 0x24,
OpGeq = 0x25,
OpConcat = 0x26,
OpUpdate = 0x27,
OpNeg = 0x28,
OpNot = 0x29,
ForceBool = 0x30,
JumpIfFalse = 0x31,
JumpIfTrue = 0x32,
Jump = 0x33,
ConcatStrings = 0x34,
ResolvePath = 0x35,
Assert = 0x36,
PushWith = 0x37,
PopWith = 0x38,
WithLookup = 0x39,
LoadBuiltins = 0x40,
LoadBuiltin = 0x41,
MkPos = 0x43,
LoadReplBinding = 0x44,
LoadScopedBinding = 0x45,
Return = 0x46,
}
struct ScopeInfo {
depth: u16,
arg_id: Option<ArgId>,
thunk_map: HashMap<ThunkId, u32>,
}
struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
ctx: &'a mut Ctx,
code: Vec<u8>,
scope_stack: Vec<ScopeInfo>,
}
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> Bytecode {
let current_dir = ctx.get_current_dir().to_string_lossy().to_string();
let mut emitter = BytecodeEmitter::new(ctx);
emitter.emit_toplevel(ir);
Bytecode {
code: emitter.code.into_boxed_slice(),
current_dir,
}
}
pub(crate) fn compile_bytecode_scoped(
ir: RawIrRef<'_>,
ctx: &mut impl BytecodeContext,
) -> Bytecode {
let current_dir = ctx.get_current_dir().to_string_lossy().to_string();
let mut emitter = BytecodeEmitter::new(ctx);
emitter.emit_toplevel_scoped(ir);
Bytecode {
code: emitter.code.into_boxed_slice(),
current_dir,
}
}
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn new(ctx: &'a mut Ctx) -> Self {
Self {
ctx,
code: Vec::with_capacity(4096),
scope_stack: Vec::with_capacity(32),
}
}
#[inline]
fn emit_op(&mut self, op: Op) {
self.code.push(op as u8);
}
#[inline]
fn emit_u8(&mut self, val: u8) {
self.code.push(val);
}
#[inline]
fn emit_u16(&mut self, val: u16) {
self.code.extend_from_slice(&val.to_le_bytes());
}
#[inline]
fn emit_u32(&mut self, val: u32) {
self.code.extend_from_slice(&val.to_le_bytes());
}
#[inline]
fn emit_i32_placeholder(&mut self) -> usize {
let offset = self.code.len();
self.code.extend_from_slice(&[0u8; 4]);
offset
}
#[inline]
fn patch_i32(&mut self, offset: usize, val: i32) {
self.code[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
}
#[inline]
fn emit_jump_placeholder(&mut self) -> usize {
self.emit_op(Op::Jump);
self.emit_i32_placeholder()
}
#[inline]
fn patch_jump_target(&mut self, placeholder_offset: usize) {
let current_pos = self.code.len();
let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4;
self.patch_i32(placeholder_offset, relative_offset);
}
fn current_depth(&self) -> u16 {
self.scope_stack.last().map_or(0, |s| s.depth)
}
fn resolve_thunk(&self, id: ThunkId) -> (u16, u32) {
for scope in self.scope_stack.iter().rev() {
if let Some(&local_idx) = scope.thunk_map.get(&id) {
let layer = self.current_depth() - scope.depth;
return (layer, local_idx);
}
}
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) {
if layer == 0 {
self.emit_op(Op::LoadLocal);
self.emit_u32(local);
} else {
self.emit_op(Op::LoadOuter);
self.emit_u8(layer as u8);
self.emit_u32(local);
}
}
fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize {
match ir.deref() {
Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body),
Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body),
Ir::If { cond, consq, alter } => {
self.count_with_thunks(*cond)
+ self.count_with_thunks(*consq)
+ self.count_with_thunks(*alter)
}
Ir::BinOp { lhs, rhs, .. } => {
self.count_with_thunks(*lhs) + self.count_with_thunks(*rhs)
}
Ir::UnOp { rhs, .. } => self.count_with_thunks(*rhs),
Ir::Call { func, arg, .. } => {
self.count_with_thunks(*func) + self.count_with_thunks(*arg)
}
Ir::Assert {
assertion, expr, ..
} => self.count_with_thunks(*assertion) + self.count_with_thunks(*expr),
Ir::Select { expr, default, .. } => {
self.count_with_thunks(*expr) + default.map_or(0, |d| self.count_with_thunks(d))
}
Ir::HasAttr { lhs, .. } => self.count_with_thunks(*lhs),
Ir::ConcatStrings { parts, .. } => {
parts.iter().map(|p| self.count_with_thunks(*p)).sum()
}
Ir::Path(p) => self.count_with_thunks(*p),
Ir::List { items } => items.iter().map(|item| self.count_with_thunks(*item)).sum(),
Ir::AttrSet { stcs, dyns } => {
stcs.iter()
.map(|(_, &(val, _))| self.count_with_thunks(val))
.sum::<usize>()
+ dyns
.iter()
.map(|&(k, v, _)| self.count_with_thunks(k) + self.count_with_thunks(v))
.sum::<usize>()
}
_ => 0,
}
}
fn collect_all_thunks<'ir>(
&self,
own_thunks: &[(ThunkId, RawIrRef<'ir>)],
body: RawIrRef<'ir>,
) -> Vec<(ThunkId, RawIrRef<'ir>)> {
let mut all = Vec::from(own_thunks);
self.collect_with_thunks_recursive(body, &mut all);
let mut i = 0;
while i < all.len() {
let thunk_body = all[i].1;
self.collect_with_thunks_recursive(thunk_body, &mut all);
i += 1;
}
all
}
fn collect_with_thunks_recursive<'ir>(
&self,
ir: RawIrRef<'ir>,
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
) {
match ir.deref() {
Ir::With { thunks, body, .. } => {
for &(id, inner) in thunks.iter() {
out.push((id, inner));
}
self.collect_with_thunks_recursive(*body, out);
}
Ir::TopLevel { thunks, body } => {
for &(id, inner) in thunks.iter() {
out.push((id, inner));
}
self.collect_with_thunks_recursive(*body, out);
}
Ir::If { cond, consq, alter } => {
self.collect_with_thunks_recursive(*cond, out);
self.collect_with_thunks_recursive(*consq, out);
self.collect_with_thunks_recursive(*alter, out);
}
Ir::BinOp { lhs, rhs, .. } => {
self.collect_with_thunks_recursive(*lhs, out);
self.collect_with_thunks_recursive(*rhs, out);
}
Ir::UnOp { rhs, .. } => self.collect_with_thunks_recursive(*rhs, out),
Ir::Call { func, arg, .. } => {
self.collect_with_thunks_recursive(*func, out);
self.collect_with_thunks_recursive(*arg, out);
}
Ir::Assert {
assertion, expr, ..
} => {
self.collect_with_thunks_recursive(*assertion, out);
self.collect_with_thunks_recursive(*expr, out);
}
Ir::Select { expr, default, .. } => {
self.collect_with_thunks_recursive(*expr, out);
if let Some(d) = default {
self.collect_with_thunks_recursive(*d, out);
}
}
Ir::HasAttr { lhs, .. } => self.collect_with_thunks_recursive(*lhs, out),
Ir::ConcatStrings { parts, .. } => {
for p in parts.iter() {
self.collect_with_thunks_recursive(*p, out);
}
}
Ir::Path(p) => self.collect_with_thunks_recursive(*p, out),
Ir::List { items } => {
for item in items.iter() {
self.collect_with_thunks_recursive(*item, out);
}
}
Ir::AttrSet { stcs, dyns } => {
for (_, &(val, _)) in stcs.iter() {
self.collect_with_thunks_recursive(val, out);
}
for &(key, val, _) in dyns.iter() {
self.collect_with_thunks_recursive(key, out);
self.collect_with_thunks_recursive(val, out);
}
}
_ => {}
}
}
fn push_scope(&mut self, has_arg: bool, arg_id: Option<ArgId>, thunk_ids: &[ThunkId]) {
let depth = self.scope_stack.len() as u16;
let thunk_base = if has_arg { 1u32 } else { 0u32 };
let thunk_map = thunk_ids
.iter()
.enumerate()
.map(|(i, &id)| (id, thunk_base + i as u32))
.collect();
self.scope_stack.push(ScopeInfo {
depth,
arg_id,
thunk_map,
});
}
fn pop_scope(&mut self) {
self.scope_stack.pop();
}
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
match ir.deref() {
Ir::TopLevel { body, thunks } => {
let with_thunk_count = self.count_with_thunks(*body);
let total_slots = thunks.len() + with_thunk_count;
let all_thunks = self.collect_all_thunks(thunks, *body);
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
self.push_scope(false, None, &thunk_ids);
if total_slots > 0 {
self.emit_op(Op::AllocLocals);
self.emit_u32(total_slots as u32);
}
self.emit_scope_thunks(thunks);
self.emit_expr(*body);
self.emit_op(Op::Return);
self.pop_scope();
}
_ => {
self.push_scope(false, None, &[]);
self.emit_expr(ir);
self.emit_op(Op::Return);
self.pop_scope();
}
}
}
fn emit_toplevel_scoped(&mut self, ir: RawIrRef<'_>) {
match ir.deref() {
Ir::TopLevel { body, thunks } => {
let with_thunk_count = self.count_with_thunks(*body);
let total_slots = thunks.len() + with_thunk_count;
let all_thunks = self.collect_all_thunks(thunks, *body);
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
self.push_scope(false, None, &thunk_ids);
if total_slots > 0 {
self.emit_op(Op::AllocLocals);
self.emit_u32(total_slots as u32);
}
self.emit_scope_thunks(thunks);
self.emit_expr(*body);
self.emit_op(Op::Return);
self.pop_scope();
}
_ => {
self.push_scope(false, None, &[]);
self.emit_expr(ir);
self.emit_op(Op::Return);
self.pop_scope();
}
}
}
fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
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 entry_point = self.code.len() as u32;
self.emit_expr(inner);
self.emit_op(Op::Return);
self.patch_jump_target(skip_patch);
self.emit_op(Op::MakeThunk);
self.emit_u32(entry_point);
self.emit_u32(label_idx);
let (_, local_idx) = self.resolve_thunk(id);
self.emit_op(Op::StoreLocal);
self.emit_u32(local_idx);
}
}
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
match ir.deref() {
&Ir::Int(x) => {
let idx = self.ctx.intern_constant(Constant::Int(x));
self.emit_op(Op::PushConst);
self.emit_u32(idx);
}
&Ir::Float(x) => {
let idx = self.ctx.intern_constant(Constant::Float(x.to_bits()));
self.emit_op(Op::PushConst);
self.emit_u32(idx);
}
&Ir::Bool(true) => self.emit_op(Op::PushTrue),
&Ir::Bool(false) => self.emit_op(Op::PushFalse),
Ir::Null => self.emit_op(Op::PushNull),
Ir::Str(s) => {
let idx = self.ctx.intern_string(s.deref());
self.emit_op(Op::PushString);
self.emit_u32(idx);
}
&Ir::Path(p) => {
self.emit_expr(p);
self.emit_op(Op::ResolvePath);
}
&Ir::If { cond, consq, alter } => {
self.emit_expr(cond);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse);
let else_placeholder = self.emit_i32_placeholder();
let after_jif = self.code.len();
self.emit_expr(consq);
self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len();
let else_offset = (after_jump as i32) - (after_jif as i32);
self.patch_i32(else_placeholder, else_offset);
self.emit_expr(alter);
let end_offset = (self.code.len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset);
}
&Ir::BinOp { lhs, rhs, kind } => {
self.emit_binop(lhs, rhs, kind);
}
&Ir::UnOp { rhs, kind } => match kind {
UnOpKind::Neg => {
self.emit_expr(rhs);
self.emit_op(Op::OpNeg);
}
UnOpKind::Not => {
self.emit_expr(rhs);
self.emit_op(Op::OpNot);
}
},
&Ir::Func {
body,
ref param,
arg,
ref thunks,
} => {
self.emit_func(arg, thunks, param, body);
}
Ir::AttrSet { stcs, dyns } => {
self.emit_attrset(stcs, dyns);
}
Ir::List { items } => {
for &item in items.iter() {
self.emit_expr(item);
}
self.emit_op(Op::MakeList);
self.emit_u32(items.len() as u32);
}
&Ir::Call { func, arg, span } => {
self.emit_expr(func);
self.emit_expr(arg);
let span_id = self.ctx.register_span(span);
self.emit_op(Op::Call);
self.emit_u32(span_id);
}
&Ir::Arg(id) => {
let (layer, local) = self.resolve_arg(id);
self.emit_load(layer, local);
}
&Ir::TopLevel { body, ref thunks } => {
self.emit_toplevel_inner(body, thunks);
}
&Ir::Select {
expr,
ref attrpath,
default,
span,
} => {
self.emit_select(expr, attrpath, default, span);
}
&Ir::Thunk(id) => {
let (layer, local) = self.resolve_thunk(id);
self.emit_load(layer, local);
}
Ir::Builtins => {
self.emit_op(Op::LoadBuiltins);
}
&Ir::Builtin(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::LoadBuiltin);
self.emit_u32(idx);
}
&Ir::ConcatStrings {
ref parts,
force_string,
} => {
for &part in parts.iter() {
self.emit_expr(part);
}
self.emit_op(Op::ConcatStrings);
self.emit_u16(parts.len() as u16);
self.emit_u8(if force_string { 1 } else { 0 });
}
&Ir::HasAttr { lhs, ref rhs } => {
self.emit_has_attr(lhs, rhs);
}
Ir::Assert {
assertion,
expr,
assertion_raw,
span,
} => {
let raw_idx = self.ctx.intern_string(assertion_raw);
let span_id = self.ctx.register_span(*span);
self.emit_expr(*assertion);
self.emit_expr(*expr);
self.emit_op(Op::Assert);
self.emit_u32(raw_idx);
self.emit_u32(span_id);
}
&Ir::CurPos(span) => {
let span_id = self.ctx.register_span(span);
self.emit_op(Op::MkPos);
self.emit_u32(span_id);
}
&Ir::ReplBinding(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::LoadReplBinding);
self.emit_u32(idx);
}
&Ir::ScopedImportBinding(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::LoadScopedBinding);
self.emit_u32(idx);
}
&Ir::With {
namespace,
body,
ref thunks,
} => {
self.emit_with(namespace, body, thunks);
}
&Ir::WithLookup(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::WithLookup);
self.emit_u32(idx);
}
}
}
fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) {
use BinOpKind::*;
match kind {
And => {
self.emit_expr(lhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse);
let skip_placeholder = self.emit_i32_placeholder();
let after_jif = self.code.len();
self.emit_expr(rhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len();
let false_offset = (after_jump as i32) - (after_jif as i32);
self.patch_i32(skip_placeholder, false_offset);
self.emit_op(Op::PushFalse);
let end_offset = (self.code.len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset);
}
Or => {
self.emit_expr(lhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfTrue);
let skip_placeholder = self.emit_i32_placeholder();
let after_jit = self.code.len();
self.emit_expr(rhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len();
let true_offset = (after_jump as i32) - (after_jit as i32);
self.patch_i32(skip_placeholder, true_offset);
self.emit_op(Op::PushTrue);
let end_offset = (self.code.len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset);
}
Impl => {
self.emit_expr(lhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse);
let skip_placeholder = self.emit_i32_placeholder();
let after_jif = self.code.len();
self.emit_expr(rhs);
self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len();
let true_offset = (after_jump as i32) - (after_jif as i32);
self.patch_i32(skip_placeholder, true_offset);
self.emit_op(Op::PushTrue);
let end_offset = (self.code.len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset);
}
PipeL => {
self.emit_expr(rhs);
self.emit_expr(lhs);
self.emit_op(Op::CallNoSpan);
}
PipeR => {
self.emit_expr(lhs);
self.emit_expr(rhs);
self.emit_op(Op::CallNoSpan);
}
_ => {
self.emit_expr(lhs);
self.emit_expr(rhs);
self.emit_op(match kind {
Add => Op::OpAdd,
Sub => Op::OpSub,
Mul => Op::OpMul,
Div => Op::OpDiv,
Eq => Op::OpEq,
Neq => Op::OpNeq,
Lt => Op::OpLt,
Gt => Op::OpGt,
Leq => Op::OpLeq,
Geq => Op::OpGeq,
Con => Op::OpConcat,
Upd => Op::OpUpdate,
_ => unreachable!(),
});
}
}
}
fn emit_func(
&mut self,
arg: ArgId,
thunks: &[(ThunkId, RawIrRef<'_>)],
param: &Option<Param<'_>>,
body: RawIrRef<'_>,
) {
let with_thunk_count = self.count_with_thunks(body);
let total_slots = thunks.len() + with_thunk_count;
let all_thunks = self.collect_all_thunks(thunks, body);
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
let skip_patch = self.emit_jump_placeholder();
let entry_point = self.code.len() as u32;
self.push_scope(true, Some(arg), &thunk_ids);
self.emit_scope_thunks(thunks);
self.emit_expr(body);
self.emit_op(Op::Return);
self.pop_scope();
self.patch_jump_target(skip_patch);
if let Some(Param {
required,
optional,
ellipsis,
}) = param
{
self.emit_op(Op::MakePatternClosure);
self.emit_u32(entry_point);
self.emit_u32(total_slots as u32);
self.emit_u16(required.len() as u16);
self.emit_u16(optional.len() as u16);
self.emit_u8(if *ellipsis { 1 } else { 0 });
for &(sym, _) in required.iter() {
let name = self.ctx.get_sym(sym).to_string();
let idx = self.ctx.intern_string(&name);
self.emit_u32(idx);
}
for &(sym, _) in optional.iter() {
let name = self.ctx.get_sym(sym).to_string();
let idx = self.ctx.intern_string(&name);
self.emit_u32(idx);
}
for &(sym, span) in required.iter().chain(optional.iter()) {
let name = self.ctx.get_sym(sym).to_string();
let name_idx = self.ctx.intern_string(&name);
let span_id = self.ctx.register_span(span);
self.emit_u32(name_idx);
self.emit_u32(span_id);
}
} else {
self.emit_op(Op::MakeClosure);
self.emit_u32(entry_point);
self.emit_u32(total_slots as u32);
}
}
fn emit_attrset(
&mut self,
stcs: &crate::ir::HashMap<'_, SymId, (RawIrRef<'_>, TextRange)>,
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
) {
if stcs.is_empty() && dyns.is_empty() {
self.emit_op(Op::MakeEmptyAttrs);
return;
}
if !dyns.is_empty() {
for (&sym, &(val, _)) in stcs.iter() {
let key = self.ctx.get_sym(sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString);
self.emit_u32(idx);
self.emit_expr(val);
}
for (_, &(_, span)) in stcs.iter() {
let span_id = self.ctx.register_span(span);
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
self.emit_op(Op::PushConst);
self.emit_u32(idx);
}
for &(key, val, span) in dyns.iter() {
self.emit_expr(key);
self.emit_expr(val);
let span_id = self.ctx.register_span(span);
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
self.emit_op(Op::PushConst);
self.emit_u32(idx);
}
self.emit_op(Op::MakeAttrsDyn);
self.emit_u32(stcs.len() as u32);
self.emit_u32(dyns.len() as u32);
} else {
for (&sym, &(val, _)) in stcs.iter() {
let key = self.ctx.get_sym(sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString);
self.emit_u32(idx);
self.emit_expr(val);
}
for (_, &(_, span)) in stcs.iter() {
let span_id = self.ctx.register_span(span);
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
self.emit_op(Op::PushConst);
self.emit_u32(idx);
}
self.emit_op(Op::MakeAttrs);
self.emit_u32(stcs.len() as u32);
}
}
fn emit_select(
&mut self,
expr: RawIrRef<'_>,
attrpath: &[Attr<RawIrRef<'_>>],
default: Option<RawIrRef<'_>>,
span: TextRange,
) {
self.emit_expr(expr);
for attr in attrpath.iter() {
match attr {
Attr::Str(sym, _) => {
let key = self.ctx.get_sym(*sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString);
self.emit_u32(idx);
}
Attr::Dynamic(expr, _) => {
self.emit_expr(*expr);
}
}
}
if let Some(default) = default {
self.emit_expr(default);
let span_id = self.ctx.register_span(span);
self.emit_op(Op::SelectDefault);
self.emit_u16(attrpath.len() as u16);
self.emit_u32(span_id);
} else {
let span_id = self.ctx.register_span(span);
self.emit_op(Op::Select);
self.emit_u16(attrpath.len() as u16);
self.emit_u32(span_id);
}
}
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
self.emit_expr(lhs);
for attr in rhs.iter() {
match attr {
Attr::Str(sym, _) => {
let key = self.ctx.get_sym(*sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString);
self.emit_u32(idx);
}
Attr::Dynamic(expr, _) => {
self.emit_expr(*expr);
}
}
}
self.emit_op(Op::HasAttr);
self.emit_u16(rhs.len() as u16);
}
fn emit_with(
&mut self,
namespace: RawIrRef<'_>,
body: RawIrRef<'_>,
thunks: &[(ThunkId, RawIrRef<'_>)],
) {
self.emit_expr(namespace);
self.emit_op(Op::PushWith);
self.emit_scope_thunks(thunks);
self.emit_expr(body);
self.emit_op(Op::PopWith);
}
fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) {
self.emit_scope_thunks(thunks);
self.emit_expr(body);
}
}

View File

@@ -1,6 +1,9 @@
use std::fmt::{self, Write as _}; use std::fmt::{self, Write as _};
use std::ops::Deref;
use std::path::Path; use std::path::Path;
use rnix::TextRange;
use crate::ir::*; use crate::ir::*;
use crate::value::Symbol; use crate::value::Symbol;
@@ -26,38 +29,23 @@ macro_rules! code {
}; };
} }
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String { pub(crate) fn compile<const SCOPED: bool>(expr: RawIrRef<'_>, ctx: &impl CodegenContext) -> String {
let mut buf = CodeBuffer::with_capacity(8192); let mut buf = CodeBuffer::with_capacity(8192);
code!(&mut buf, ctx; "(()=>{"); code!(
&mut buf, ctx;
code!(&mut buf, ctx; "((" { if SCOPED { "_s" } else { "" } } ")=>{"
"const _d=" "const _d="
quoted(&ctx.get_current_dir().display().to_string()) quoted(&ctx.get_current_dir().display().to_string())
",_w=null;return " ",_w=null;"
expr "return " expr
"})()"); "})" { if SCOPED { "" } else { "()" } }
buf.into_string()
}
pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String {
let mut buf = CodeBuffer::with_capacity(8192);
code!(&mut buf, ctx; "((_s)=>{");
code!(&mut buf, ctx;
"const _d="
quoted(&ctx.get_current_dir().display().to_string())
",_w=null;return "
expr
"})"
); );
buf.into_string() buf.into_string()
} }
pub(crate) struct CodeBuffer { struct CodeBuffer {
buf: String, buf: String,
} }
@@ -188,7 +176,6 @@ impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange {
} }
pub(crate) trait CodegenContext { pub(crate) trait CodegenContext {
fn get_ir(&self, id: ExprId) -> &Ir;
fn get_sym(&self, id: SymId) -> Symbol<'_>; fn get_sym(&self, id: SymId) -> Symbol<'_>;
fn get_current_dir(&self) -> &Path; fn get_current_dir(&self) -> &Path;
fn get_store_dir(&self) -> &str; fn get_store_dir(&self) -> &str;
@@ -196,83 +183,96 @@ pub(crate) trait CodegenContext {
fn register_span(&self, range: rnix::TextRange) -> usize; fn register_span(&self, range: rnix::TextRange) -> usize;
} }
impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
ctx.get_ir(*self).compile(ctx, buf);
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Symbol<'_> { impl<Ctx: CodegenContext> Compile<Ctx> for Symbol<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
quoted(self).compile(ctx, buf); quoted(self).compile(ctx, buf);
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Ir { impl<Ctx: CodegenContext> Compile<Ctx> for RawIrRef<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
match self { match self.deref() {
Ir::Int(int) => { Ir::Int(int) => {
code!(buf, "{}n", int.inner); code!(buf, "{}n", int);
} }
Ir::Float(float) => { Ir::Float(float) => {
code!(buf, "{}", float.inner); code!(buf, "{}", float);
} }
Ir::Bool(bool) => { Ir::Bool(bool) => {
code!(buf, "{}", bool.inner); code!(buf, "{}", bool);
} }
Ir::Null(_) => { Ir::Null => {
code!(buf, ctx; "null"); code!(buf, ctx; "null");
} }
Ir::Str(s) => { Ir::Str(s) => {
code!(buf, ctx; quoted(&s.val)); code!(buf, ctx; quoted(s));
} }
Ir::Path(p) => { Ir::Path(p) => {
// Nix.resolvePath // Nix.resolvePath
code!(buf, ctx; "$r(_d," ctx.get_ir(p.expr) ")"); code!(buf, ctx; "$r(_d," p ")");
} }
Ir::If(x) => x.compile(ctx, buf), Ir::If { cond, consq, alter } => {
Ir::BinOp(x) => x.compile(ctx, buf), code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")");
Ir::UnOp(x) => x.compile(ctx, buf),
Ir::Func(x) => x.compile(ctx, buf),
Ir::AttrSet(x) => x.compile(ctx, buf),
Ir::List(x) => x.compile(ctx, buf),
Ir::Call(x) => x.compile(ctx, buf),
Ir::Arg(x) => {
code!(buf, "arg{}", x.inner.0);
} }
Ir::TopLevel(x) => x.compile(ctx, buf), &Ir::BinOp { lhs, rhs, kind } => compile_binop(lhs, rhs, kind, ctx, buf),
Ir::Select(x) => x.compile(ctx, buf), &Ir::UnOp { rhs, kind } => compile_unop(rhs, kind, ctx, buf),
&Ir::Thunk(Thunk { inner: expr_id, .. }) => { &Ir::Func {
code!(buf, "expr{}", expr_id.0); body,
} ref param,
Ir::Builtins(_) => { arg,
// Nix.builtins ref thunks,
code!(buf, ctx; "$b"); } => compile_func(arg, thunks, param, body, ctx, buf),
} Ir::AttrSet { stcs, dyns } => compile_attrset(stcs, dyns, ctx, buf),
&Ir::Builtin(Builtin { inner: name, .. }) => { Ir::List { items } => compile_list(items, ctx, buf),
// Nix.builtins Ir::Call { func, arg, span } => {
code!(buf, ctx; code!(buf, ctx;
"$b.get(" "$c("
ctx.get_sym(name) func
","
arg
","
span
")" ")"
); );
} }
Ir::ConcatStrings(x) => x.compile(ctx, buf), Ir::Arg(x) => {
Ir::HasAttr(x) => x.compile(ctx, buf), code!(buf, "a{}", x.0);
&Ir::Assert(Assert { }
&Ir::TopLevel { body, ref thunks } => compile_toplevel(body, thunks, ctx, buf),
&Ir::Select {
expr,
ref attrpath,
default,
span,
} => compile_select(expr, attrpath, default, span, ctx, buf),
Ir::Thunk(ThunkId(id)) => {
code!(buf, "e{}", id);
}
Ir::Builtins => {
// Nix.builtins
code!(buf, ctx; "$b");
}
&Ir::Builtin(name) => {
// Nix.builtins
code!(buf, ctx; "$b.get(" ctx.get_sym(name) ")");
}
&Ir::ConcatStrings {
ref parts,
force_string,
} => compile_concat_strings(parts, force_string, ctx, buf),
&Ir::HasAttr { lhs, ref rhs } => compile_has_attr(lhs, rhs, ctx, buf),
Ir::Assert {
assertion, assertion,
expr, expr,
ref assertion_raw, assertion_raw,
span: assert_span, span: assert_span,
}) => { } => {
let assertion = ctx.get_ir(assertion);
// Nix.assert // Nix.assert
code!(buf, ctx; code!(buf, ctx;
"$a(" "$a("
assertion assertion
"," ","
ctx.get_ir(expr) expr
"," ","
quoted(assertion_raw) quoted(assertion_raw)
"," ","
@@ -280,365 +280,339 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
")" ")"
); );
} }
Ir::CurPos(cur_pos) => { Ir::CurPos(span) => {
// Nix.mkPos // Nix.mkPos
code!(buf, ctx; code!(buf, ctx; "$mp(" span ")");
"$mp("
cur_pos.span
")"
);
} }
&Ir::ReplBinding(ReplBinding { inner: name, .. }) => { &Ir::ReplBinding(name) => {
// Nix.getReplBinding // Nix.getReplBinding
code!(buf, ctx; code!(buf, ctx; "$gb(" ctx.get_sym(name) ")");
"$gb("
ctx.get_sym(name)
")"
);
} }
&Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { &Ir::ScopedImportBinding(name) => {
code!(buf, ctx; code!(buf, ctx; "_s.get(" ctx.get_sym(name) ")");
"_s.get("
ctx.get_sym(name)
")"
);
} }
Ir::WithExpr(x) => x.compile(ctx, buf), &Ir::With {
&Ir::WithLookup(WithLookup { inner: name, .. }) => { namespace,
body,
ref thunks,
} => compile_with(namespace, body, thunks, ctx, buf),
&Ir::WithLookup(name) => {
// Nix.lookupWith // Nix.lookupWith
code!(buf, ctx; code!(buf, ctx; "$l(" ctx.get_sym(name) ",_w)");
"$l("
ctx.get_sym(name)
",_w)"
);
} }
} }
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for If { fn compile_binop<'ir>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { lhs: RawIrRef<'ir>,
let &If { rhs: RawIrRef<'ir>,
cond, kind: BinOpKind,
consq, ctx: &impl CodegenContext,
alter, buf: &mut CodeBuffer,
span: _, ) {
} = self; use BinOpKind::*;
let cond = ctx.get_ir(cond); match kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
let op_func = match kind {
Add => "$oa",
Sub => "$os",
Mul => "$om",
Div => "$od",
Eq => "$oe",
Neq => "!$oe",
Lt => "$ol",
Gt => "$og",
Leq => "!$og",
Geq => "!$ol",
Con => "$oc",
Upd => "$ou",
_ => unreachable!(),
};
// Nix.forceBool code!(
code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")"); buf, ctx;
} op_func "(" lhs "," rhs ")"
}
impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
use BinOpKind::*;
let lhs = ctx.get_ir(self.lhs);
let rhs = ctx.get_ir(self.rhs);
match self.kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
let op_func = match self.kind {
Add => "$oa",
Sub => "$os",
Mul => "$om",
Div => "$od",
Eq => "$oe",
Neq => "!$oe",
Lt => "$ol",
Gt => "$og",
Leq => "!$og",
Geq => "!$ol",
Con => "$oc",
Upd => "$ou",
_ => unreachable!(),
};
code!(
buf, ctx;
op_func "(" lhs "," rhs ")"
);
}
And => {
code!(
buf, ctx;
"$fb(" lhs ")" "&&" "$fb(" rhs ")"
);
}
Or => {
code!(
buf, ctx;
"$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
Impl => {
code!(
buf, ctx;
"!$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
PipeL => {
code!(buf, ctx; "$c(" rhs "," lhs ")");
}
PipeR => {
code!(buf, ctx; "$c(" lhs "," rhs ")");
}
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
use UnOpKind::*;
let rhs = ctx.get_ir(self.rhs);
match self.kind {
Neg => {
code!(buf, ctx; "$os(0n," rhs ")");
}
Not => {
code!(buf, ctx; "!$fb(" ctx.get_ir(self.rhs) ")");
}
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
let has_thunks = !self.thunks.is_empty();
if let Some(Param {
required,
optional,
ellipsis,
}) = &self.param
{
code!(buf, "$mf(arg{}=>", id);
if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else {
code!(buf, ctx; "(" self.body ")");
}
code!(buf, ctx;
",["
joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym));
})
"],["
joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym));
})
"],new Map(["
joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),"
ellipsis
")"
); );
} else { }
code!(buf, "arg{}=>", id); And => {
if has_thunks { code!(
code!(buf, ctx; "{" self.thunks "return " self.body "}"); buf, ctx;
} else { "$fb(" lhs ")" "&&" "$fb(" rhs ")"
code!(buf, ctx; "(" self.body ")"); );
} }
Or => {
code!(
buf, ctx;
"$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
Impl => {
code!(
buf, ctx;
"!$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
PipeL => {
code!(buf, ctx; "$c(" rhs "," lhs ")");
}
PipeR => {
code!(buf, ctx; "$c(" lhs "," rhs ")");
} }
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Call { fn compile_unop(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { rhs: RawIrRef<'_>,
kind: UnOpKind,
ctx: &impl CodegenContext,
buf: &mut CodeBuffer,
) {
use UnOpKind::*;
match kind {
Neg => {
// 0 - rhs
code!(buf, ctx; "$os(0n," rhs ")");
}
Not => {
code!(buf, ctx; "!$fb(" rhs ")");
}
}
}
fn compile_func<'ir, Ctx: CodegenContext>(
ArgId(id): ArgId,
thunks: &[(ThunkId, RawIrRef<'ir>)],
param: &Option<Param<'ir>>,
body: RawIrRef<'ir>,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
let has_thunks = !thunks.is_empty();
if let Some(Param {
required,
optional,
ellipsis,
}) = &param
{
code!(buf, "$mf(a{}=>", id);
if has_thunks {
code!(buf, ctx; "{" thunks "return " body "}");
} else {
code!(buf, ctx; "(" body ")");
}
code!(buf, ctx; code!(buf, ctx;
"$c(" ",["
ctx.get_ir(self.func) joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
"," code!(buf, ctx; ctx.get_sym(sym));
ctx.get_ir(self.arg) })
"," "],["
self.span joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym));
})
"],new Map(["
joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),"
ellipsis
")" ")"
); );
} else {
code!(buf, "a{}=>", id);
if has_thunks {
code!(buf, ctx; "{" thunks "return " body "}");
} else {
code!(buf, ctx; "(" body ")");
}
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for [(ExprId, ExprId)] { impl<'ir, Ctx: CodegenContext> Compile<Ctx> for [(ThunkId, RawIrRef<'ir>)] {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if self.is_empty() { if self.is_empty() {
return; return;
} }
for &(slot, inner) in self { code!(
let inner_ir = ctx.get_ir(inner); buf, ctx;
code!( "const "
buf, ctx; joined(self.iter(), ",", |ctx: &Ctx, buf, &(slot, inner)| {
"let expr" slot.0 "=$t(()=>(" inner_ir ")," code!(buf, ctx; "e" slot.0 "=$t(()=>(" inner ")," "'e" slot.0 "')");
"\"expr" slot.0 "\");"
);
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if self.thunks.is_empty() {
ctx.get_ir(self.body).compile(ctx, buf);
} else {
let body = ctx.get_ir(self.body);
code!(buf, ctx; "(()=>{" self.thunks "return " body "})()");
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for WithExpr {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let namespace = ctx.get_ir(self.namespace);
let body = ctx.get_ir(self.body);
let has_thunks = !self.thunks.is_empty();
if has_thunks {
code!(buf, ctx; "((_w)=>{" self.thunks "return " body "})({env:" namespace ",last:_w})");
} else {
code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})");
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Select {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if let Some(default) = self.default {
code!(buf, ctx;
"$sd("
ctx.get_ir(self.expr)
",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
}
})
"],"
ctx.get_ir(default)
","
self.span
")"
);
} else {
code!(buf, ctx;
"$s("
ctx.get_ir(self.expr)
",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; 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 {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if !self.dyns.is_empty() {
code!(buf, ctx;
"$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),{dynKeys:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; ctx.get_ir(*key));
})
"],dynVals:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
let val = ctx.get_ir(*val);
code!(buf, ctx; 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;
"$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]))"
);
} else {
code!(buf, ctx; "new Map()");
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for List {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"["
joined(self.items.iter(), ",", |ctx: &Ctx, buf, item| {
let item = ctx.get_ir(*item);
code!(buf, ctx; item);
}) })
"]" ";"
); );
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings { fn compile_toplevel<'ir, Ctx: CodegenContext>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { body: RawIrRef<'ir>,
code!(buf, ctx; thunks: &[(ThunkId, RawIrRef<'ir>)],
"$cs([" ctx: &Ctx,
joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| { buf: &mut CodeBuffer,
let part = ctx.get_ir(*part); ) {
code!(buf, ctx; part); if thunks.is_empty() {
}) body.compile(ctx, buf);
"]," self.force_string ")" } else {
); code!(buf, ctx; "(()=>{" thunks "return " body "})()");
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr { fn compile_with<'ir>(
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { namespace: RawIrRef<'ir>,
body: RawIrRef<'ir>,
thunks: &[(ThunkId, RawIrRef<'ir>)],
ctx: &impl CodegenContext,
buf: &mut CodeBuffer,
) {
let has_thunks = !thunks.is_empty();
if has_thunks {
code!(buf, ctx; "((_w)=>{" thunks "return " body "})({env:" namespace ",last:_w})");
} else {
code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})");
}
}
fn compile_select<'ir, Ctx: CodegenContext>(
expr: RawIrRef<'ir>,
attrpath: &[Attr<RawIrRef<'ir>>],
default: Option<RawIrRef<'ir>>,
span: TextRange,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if let Some(default) = default {
code!(buf, ctx; code!(buf, ctx;
"$h(" "$sd("
ctx.get_ir(self.lhs) expr
",[" ",["
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr { match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)), Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)), Attr::Dynamic(expr_id, _) => code!(buf, ctx; *expr_id),
} }
}) })
"])" "],"
default
","
span
")"
);
} else {
code!(buf, ctx;
"$s("
expr
",["
joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr, _) => code!(buf, ctx; expr),
}
})
"],"
span
")"
); );
} }
} }
fn compile_attrset<'ir, Ctx: CodegenContext>(
stcs: &HashMap<'ir, SymId, (RawIrRef<'ir>, TextRange)>,
dyns: &[(RawIrRef<'ir>, RawIrRef<'ir>, TextRange)],
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if !dyns.is_empty() {
code!(buf, ctx;
"$ma(new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),{dynKeys:["
joined(dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; key);
})
"],dynVals:["
joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
code!(buf, ctx; val);
})
"],dynSpans:["
joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
code!(buf, ctx; attr_span);
})
"]})"
);
} else if !stcs.is_empty() {
code!(buf, ctx;
"$ma(new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]))"
);
} else {
code!(buf, ctx; "$e");
}
}
fn compile_list<Ctx: CodegenContext>(items: &[RawIrRef<'_>], ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"["
joined(items.iter(), ",", |ctx: &Ctx, buf, item| {
code!(buf, ctx; item);
})
"]"
);
}
fn compile_concat_strings<Ctx: CodegenContext>(
parts: &[RawIrRef<'_>],
force_string: bool,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
code!(buf, ctx;
"$cs(["
joined(parts.iter(), ",", |ctx: &Ctx, buf, part| {
code!(buf, ctx; part);
})
"]," force_string ")"
);
}
fn compile_has_attr<'ir, Ctx: CodegenContext>(
lhs: RawIrRef<'ir>,
rhs: &[Attr<RawIrRef<'ir>>],
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
code!(buf, ctx;
"$h("
lhs
",["
joined(rhs.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr, _) => code!(buf, ctx; expr),
}
})
"])"
);
}

View File

@@ -1,21 +1,22 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::hash::BuildHasher;
use std::path::Path; use std::path::Path;
use std::ptr::NonNull;
use hashbrown::{HashMap, HashSet}; use bumpalo::Bump;
use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
use rnix::TextRange; use rnix::TextRange;
use string_interner::DefaultStringInterner; use string_interner::DefaultStringInterner;
use crate::codegen::{CodegenContext, compile, compile_scoped}; use crate::bytecode::{self, Bytecode, BytecodeContext, Constant};
use crate::codegen::{CodegenContext, compile};
use crate::disassembler::{Disassembler, DisassemblerContext};
use crate::downgrade::*; use crate::downgrade::*;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::ir::{ use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq};
Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk,
ToIr as _, WithLookup,
};
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
use crate::runtime::inspector::InspectorServer; use crate::runtime::inspector::InspectorServer;
use crate::runtime::{Runtime, RuntimeContext}; use crate::runtime::{ForceMode, Runtime, RuntimeContext};
use crate::store::{DaemonStore, Store, StoreConfig}; use crate::store::{DaemonStore, Store, StoreConfig};
use crate::value::{Symbol, Value}; use crate::value::{Symbol, Value};
@@ -54,16 +55,16 @@ pub struct Context {
_inspector_server: Option<InspectorServer>, _inspector_server: Option<InspectorServer>,
} }
macro_rules! eval { macro_rules! eval_bc {
($name:ident, $wrapper:literal) => { ($name:ident, $mode:expr) => {
pub fn $name(&mut self, source: Source) -> Result<Value> { pub fn $name(&mut self, source: Source) -> Result<Value> {
tracing::info!("Starting evaluation"); tracing::info!("Starting evaluation");
tracing::debug!("Compiling code"); tracing::debug!("Compiling bytecode");
let code = self.compile(source)?; let bytecode = self.ctx.compile_bytecode(source)?;
tracing::debug!("Executing JavaScript"); tracing::debug!("Executing bytecode");
self.runtime.eval(format!($wrapper, code), &mut self.ctx) self.runtime.eval_bytecode(bytecode, &mut self.ctx, $mode)
} }
}; };
} }
@@ -138,10 +139,9 @@ impl Context {
Ok(()) Ok(())
} }
eval!(eval, "Nix.force({})"); eval_bc!(eval, ForceMode::Force);
eval!(eval_shallow, "Nix.forceShallow({})"); eval_bc!(eval_shallow, ForceMode::ForceShallow);
eval!(eval_deep, "Nix.forceDeep({})"); eval_bc!(eval_deep, ForceMode::ForceDeep);
pub fn eval_repl<'a>(&'a mut self, source: Source, scope: &'a HashSet<SymId>) -> Result<Value> { pub fn eval_repl<'a>(&'a mut self, source: Source, scope: &'a HashSet<SymId>) -> Result<Value> {
tracing::info!("Starting evaluation"); tracing::info!("Starting evaluation");
@@ -157,6 +157,18 @@ impl Context {
self.ctx.compile(source, None) self.ctx.compile(source, None)
} }
pub fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode> {
self.ctx.compile_bytecode(source)
}
pub fn disassemble(&self, bytecode: &Bytecode) -> String {
Disassembler::new(bytecode, &self.ctx).disassemble()
}
pub fn disassemble_colored(&self, bytecode: &Bytecode) -> String {
Disassembler::new(bytecode, &self.ctx).disassemble_colored()
}
pub fn get_store_dir(&self) -> &str { pub fn get_store_dir(&self) -> &str {
self.ctx.get_store_dir() self.ctx.get_store_dir()
} }
@@ -182,33 +194,44 @@ impl Context {
} }
} }
pub(crate) struct Ctx { struct Ctx {
irs: Vec<Ir>,
symbols: DefaultStringInterner, symbols: DefaultStringInterner,
global: NonNull<HashMap<SymId, ExprId>>, global: HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
sources: Vec<Source>, sources: Vec<Source>,
store: DaemonStore, store: DaemonStore,
spans: UnsafeCell<Vec<(usize, TextRange)>>, spans: UnsafeCell<Vec<(usize, TextRange)>>,
thunk_count: usize,
global_strings: Vec<String>,
global_string_map: HashMap<String, u32>,
global_constants: Vec<Constant>,
global_constant_map: HashMap<Constant, u32>,
synced_strings: usize,
synced_constants: usize,
}
/// Owns the bump allocator and a read-only reference into it.
///
/// # Safety
/// The `ir` field points into `_bump`'s storage. We use `'static` as a sentinel
/// lifetime because the struct owns the backing memory. The `as_ref` method
/// re-binds the lifetime to `&self`, preventing use-after-free.
struct OwnedIr {
_bump: Bump,
ir: RawIrRef<'static>,
}
impl OwnedIr {
fn as_ref(&self) -> RawIrRef<'_> {
self.ir
}
} }
impl Ctx { impl Ctx {
fn new() -> Result<Self> { fn new() -> Result<Self> {
use crate::ir::{Builtins, ToIr as _};
let mut symbols = DefaultStringInterner::new(); let mut symbols = DefaultStringInterner::new();
let mut irs = Vec::new();
let mut global = HashMap::new(); let mut global = HashMap::new();
irs.push(
Builtins {
span: rnix::TextRange::default(),
}
.to_ir(),
);
let builtins_expr = ExprId(0);
let builtins_sym = symbols.get_or_intern("builtins"); let builtins_sym = symbols.get_or_intern("builtins");
global.insert(builtins_sym, builtins_expr); global.insert(builtins_sym, Ir::Builtins);
let free_globals = [ let free_globals = [
"abort", "abort",
@@ -232,48 +255,19 @@ impl Ctx {
"toString", "toString",
]; ];
let consts = [ let consts = [
( ("true", Ir::Bool(true)),
"true", ("false", Ir::Bool(false)),
Bool { ("null", Ir::Null),
inner: true,
span: rnix::TextRange::default(),
}
.to_ir(),
),
(
"false",
Bool {
inner: false,
span: rnix::TextRange::default(),
}
.to_ir(),
),
(
"null",
Null {
span: rnix::TextRange::default(),
}
.to_ir(),
),
]; ];
for name in free_globals { for name in free_globals {
let name_sym = symbols.get_or_intern(name); let name = symbols.get_or_intern(name);
let id = ExprId(irs.len()); let value = Ir::Builtin(name);
irs.push( global.insert(name, value);
Builtin {
inner: name_sym,
span: rnix::TextRange::default(),
}
.to_ir(),
);
global.insert(name_sym, id);
} }
for (name, value) in consts { for (name, value) in consts {
let name_sym = symbols.get_or_intern(name); let name = symbols.get_or_intern(name);
let id = ExprId(irs.len()); global.insert(name, value);
irs.push(value);
global.insert(name_sym, id);
} }
let config = StoreConfig::from_env(); let config = StoreConfig::from_env();
@@ -281,20 +275,39 @@ impl Ctx {
Ok(Self { Ok(Self {
symbols, symbols,
irs, global,
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
sources: Vec::new(), sources: Vec::new(),
store, store,
spans: UnsafeCell::new(Vec::new()), spans: UnsafeCell::new(Vec::new()),
thunk_count: 0,
global_strings: Vec::new(),
global_string_map: HashMap::new(),
global_constants: Vec::new(),
global_constant_map: HashMap::new(),
synced_strings: 0,
synced_constants: 0,
}) })
} }
fn downgrade_ctx<'a>(&'a mut self, extra_scope: Option<Scope<'a>>) -> DowngradeCtx<'a> { fn downgrade_ctx<'ctx, 'id, 'ir>(
let global_ref = unsafe { self.global.as_ref() }; &'ctx mut self,
DowngradeCtx::new(self, global_ref, extra_scope) bump: &'ir Bump,
token: GhostToken<'id>,
extra_scope: Option<Scope<'ctx>>,
) -> DowngradeCtx<'ctx, 'id, 'ir> {
let source = self.get_current_source();
DowngradeCtx::new(
bump,
token,
&mut self.symbols,
&self.global,
extra_scope,
&mut self.thunk_count,
source,
)
} }
pub(crate) fn get_current_dir(&self) -> &Path { fn get_current_dir(&self) -> &Path {
self.sources self.sources
.last() .last()
.as_ref() .as_ref()
@@ -302,18 +315,18 @@ impl Ctx {
.get_dir() .get_dir()
} }
pub(crate) fn get_current_source(&self) -> Source { fn get_current_source(&self) -> Source {
self.sources self.sources
.last() .last()
.expect("current_source is not set") .expect("current_source is not set")
.clone() .clone()
} }
pub(crate) fn get_source(&self, id: usize) -> Source { fn downgrade<'ctx>(
self.sources.get(id).expect("source not found").clone() &'ctx mut self,
} source: Source,
extra_scope: Option<Scope<'ctx>>,
fn downgrade<'a>(&mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<ExprId> { ) -> Result<OwnedIr> {
tracing::debug!("Parsing Nix expression"); tracing::debug!("Parsing Nix expression");
self.sources.push(source.clone()); self.sources.push(source.clone());
@@ -326,18 +339,29 @@ impl Ctx {
.tree() .tree()
.expr() .expr()
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?; .ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
self.downgrade_ctx(extra_scope).downgrade(expr) let bump = Bump::new();
GhostToken::new(|token| {
let ir = self
.downgrade_ctx(&bump, token, extra_scope)
.downgrade_toplevel(expr)?;
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
Ok(OwnedIr { _bump: bump, ir })
})
} }
fn compile<'a>(&'a mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<String> { fn compile<'ctx>(
&'ctx mut self,
source: Source,
extra_scope: Option<Scope<'ctx>>,
) -> Result<String> {
let root = self.downgrade(source, extra_scope)?; let root = self.downgrade(source, extra_scope)?;
tracing::debug!("Generating JavaScript code"); tracing::debug!("Generating JavaScript code");
let code = compile(self.get_ir(root), self); let code = compile::<false>(root.as_ref(), self);
tracing::debug!("Generated code: {}", &code); tracing::debug!("Generated code: {}", &code);
Ok(code) Ok(code)
} }
pub(crate) fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> { fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
let scope = Scope::ScopedImport( let scope = Scope::ScopedImport(
scope scope
.into_iter() .into_iter()
@@ -346,16 +370,33 @@ impl Ctx {
); );
let root = self.downgrade(source, Some(scope))?; let root = self.downgrade(source, Some(scope))?;
tracing::debug!("Generating JavaScript code for scoped import"); tracing::debug!("Generating JavaScript code for scoped import");
let code = compile_scoped(self.get_ir(root), self); let code = compile::<true>(root.as_ref(), self);
tracing::debug!("Generated scoped code: {}", &code); tracing::debug!("Generated scoped code: {}", &code);
Ok(code) Ok(code)
} }
fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode> {
let root = self.downgrade(source, None)?;
tracing::debug!("Generating bytecode");
let bytecode = bytecode::compile_bytecode(root.as_ref(), self);
tracing::debug!("Compiled bytecode: {:#04X?}", bytecode.code);
Ok(bytecode)
}
fn compile_bytecode_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<Bytecode> {
let scope = Scope::ScopedImport(
scope
.into_iter()
.map(|k| self.symbols.get_or_intern(k))
.collect(),
);
let root = self.downgrade(source, Some(scope))?;
tracing::debug!("Generating bytecode for scoped import");
Ok(bytecode::compile_bytecode_scoped(root.as_ref(), self))
}
} }
impl CodegenContext for Ctx { impl CodegenContext for Ctx {
fn get_ir(&self, id: ExprId) -> &Ir {
self.irs.get(id.0).expect("ExprId out of bounds")
}
fn get_sym(&self, id: SymId) -> Symbol<'_> { fn get_sym(&self, id: SymId) -> Symbol<'_> {
self.symbols self.symbols
.resolve(id) .resolve(id)
@@ -382,6 +423,40 @@ impl CodegenContext for Ctx {
} }
} }
impl BytecodeContext for Ctx {
fn intern_string(&mut self, s: &str) -> u32 {
if let Some(&idx) = self.global_string_map.get(s) {
return idx;
}
let idx = self.global_strings.len() as u32;
self.global_strings.push(s.to_string());
self.global_string_map.insert(s.to_string(), idx);
idx
}
fn intern_constant(&mut self, c: Constant) -> u32 {
if let Some(&idx) = self.global_constant_map.get(&c) {
return idx;
}
let idx = self.global_constants.len() as u32;
self.global_constants.push(c.clone());
self.global_constant_map.insert(c, idx);
idx
}
fn register_span(&self, range: TextRange) -> u32 {
CodegenContext::register_span(self, range) as u32
}
fn get_sym(&self, id: SymId) -> &str {
self.symbols.resolve(id).expect("SymId out of bounds")
}
fn get_current_dir(&self) -> &Path {
Ctx::get_current_dir(self)
}
}
impl RuntimeContext for Ctx { impl RuntimeContext for Ctx {
fn get_current_dir(&self) -> &Path { fn get_current_dir(&self) -> &Path {
self.get_current_dir() self.get_current_dir()
@@ -395,160 +470,239 @@ impl RuntimeContext for Ctx {
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> { fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
self.compile_scoped(source, scope) self.compile_scoped(source, scope)
} }
fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode> {
self.compile_bytecode(source)
}
fn compile_bytecode_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<Bytecode> {
self.compile_bytecode_scoped(source, scope)
}
fn get_source(&self, id: usize) -> Source { fn get_source(&self, id: usize) -> Source {
self.get_source(id) self.sources.get(id).expect("source not found").clone()
} }
fn get_store(&self) -> &DaemonStore { fn get_store(&self) -> &DaemonStore {
&self.store &self.store
} }
fn get_span(&self, id: usize) -> (usize, rnix::TextRange) { fn get_span(&self, id: usize) -> (usize, TextRange) {
let spans = unsafe { &*self.spans.get() }; let spans = unsafe { &*self.spans.get() };
spans[id] spans[id]
} }
fn get_unsynced(&mut self) -> (&[String], &[Constant], usize, usize) {
let strings_base = self.synced_strings;
let constants_base = self.synced_constants;
let new_strings = &self.global_strings[strings_base..];
let new_constants = &self.global_constants[constants_base..];
self.synced_strings = self.global_strings.len();
self.synced_constants = self.global_constants.len();
(new_strings, new_constants, strings_base, constants_base)
}
}
impl DisassemblerContext for Ctx {
fn lookup_string(&self, id: u32) -> &str {
self.global_strings
.get(id as usize)
.expect("string not found")
}
fn lookup_constant(&self, id: u32) -> &Constant {
self.global_constants
.get(id as usize)
.expect("constant not found")
}
} }
enum Scope<'ctx> { enum Scope<'ctx> {
Global(&'ctx HashMap<SymId, ExprId>), Global(&'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>),
Repl(&'ctx HashSet<SymId>), Repl(&'ctx HashSet<SymId>),
ScopedImport(HashSet<SymId>), ScopedImport(HashSet<SymId>),
Let(HashMap<SymId, ExprId>), Let(HashMap<SymId, ThunkId>),
Param(SymId, ExprId), Param(SymId, ArgId),
} }
struct ScopeGuard<'a, 'ctx> { struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
ctx: &'a mut DowngradeCtx<'ctx>, ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
} }
impl<'a, 'ctx> Drop for ScopeGuard<'a, 'ctx> { impl Drop for ScopeGuard<'_, '_, '_, '_> {
fn drop(&mut self) { fn drop(&mut self) {
self.ctx.scopes.pop(); self.ctx.scopes.pop();
} }
} }
impl<'a, 'ctx> ScopeGuard<'a, 'ctx> { impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx> { fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
self.ctx self.ctx
} }
} }
pub struct DowngradeCtx<'ctx> { struct ThunkScope<'id, 'ir> {
ctx: &'ctx mut Ctx, bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
irs: Vec<Ir>, cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>,
hasher: DefaultHashBuilder,
}
impl<'id, 'ir> ThunkScope<'id, 'ir> {
fn new_in(bump: &'ir Bump) -> Self {
Self {
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> {
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));
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>)>) {
self.bindings.extend(iter);
}
}
struct DowngradeCtx<'ctx, 'id, 'ir> {
bump: &'ir Bump,
token: GhostToken<'id>,
symbols: &'ctx mut DefaultStringInterner,
source: Source,
scopes: Vec<Scope<'ctx>>, scopes: Vec<Scope<'ctx>>,
with_scope_count: usize, with_scope_count: usize,
arg_id: usize, arg_count: usize,
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>, thunk_count: &'ctx mut usize,
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
} }
impl<'ctx> DowngradeCtx<'ctx> { fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
fn new( !matches!(
ctx: &'ctx mut Ctx, ir.borrow(token),
global: &'ctx HashMap<SymId, ExprId>, Ir::Builtin(_)
extra_scope: Option<Scope<'ctx>>, | Ir::Builtins
) -> Self {
Self {
scopes: std::iter::once(Scope::Global(global))
.chain(extra_scope)
.collect(),
irs: vec![],
arg_id: 0,
with_scope_count: 0,
thunk_scopes: vec![Vec::new()],
ctx,
}
}
}
impl DowngradeContext for DowngradeCtx<'_> {
fn new_expr(&mut self, expr: Ir) -> ExprId {
self.irs.push(expr);
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
}
fn new_arg(&mut self, span: TextRange) -> ExprId {
self.irs.push(
Arg {
inner: ArgId(self.arg_id),
span,
}
.to_ir(),
);
self.arg_id += 1;
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
}
fn get_ir(&self, id: ExprId) -> &Ir {
if id.0 < self.ctx.irs.len() {
self.ctx.irs.get(id.0).expect("unreachable")
} else {
self.irs
.get(id.0 - self.ctx.irs.len())
.expect("ExprId out of bounds")
}
}
fn maybe_thunk(&mut self, id: ExprId) -> ExprId {
let ir = self.get_ir(id);
match ir {
Ir::Builtin(_)
| Ir::Builtins(_)
| Ir::Int(_) | Ir::Int(_)
| Ir::Float(_) | Ir::Float(_)
| Ir::Bool(_) | Ir::Bool(_)
| Ir::Null(_) | Ir::Null
| Ir::Str(_) | Ir::Str(_)
| Ir::Thunk(_) => id, | Ir::Thunk(_)
_ => { )
let span = ir.span(); }
let slot = self.reserve_slots(1).next().expect("reserve_slots failed");
self.replace_ir(slot, Thunk { inner: slot, span }.to_ir()); impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
self.register_thunk(slot, id); fn new(
slot bump: &'ir Bump,
} token: GhostToken<'id>,
symbols: &'ctx mut DefaultStringInterner,
global: &'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
extra_scope: Option<Scope<'ctx>>,
thunk_count: &'ctx mut usize,
source: Source,
) -> Self {
Self {
bump,
token,
symbols,
source,
scopes: std::iter::once(Scope::Global(global))
.chain(extra_scope)
.collect(),
thunk_count,
arg_count: 0,
with_scope_count: 0,
thunk_scopes: vec![ThunkScope::new_in(bump)],
} }
} }
}
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
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> {
if !should_thunk(ir, &self.token) {
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);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
self.thunk_scopes
.last_mut()
.expect("no active cache scope")
.add_binding(id, ir, &self.token);
IrRef::alloc(self.bump, Ir::Thunk(id))
}
fn new_sym(&mut self, sym: String) -> SymId { fn new_sym(&mut self, sym: String) -> SymId {
self.ctx.symbols.get_or_intern(sym) self.symbols.get_or_intern(sym)
} }
fn get_sym(&self, id: SymId) -> Symbol<'_> { fn get_sym(&self, id: SymId) -> Symbol<'_> {
self.ctx.get_sym(id) self.symbols.resolve(id).expect("no symbol found").into()
} }
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId> { fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
for scope in self.scopes.iter().rev() { for scope in self.scopes.iter().rev() {
match scope { match scope {
&Scope::Global(global_scope) => { &Scope::Global(global_scope) => {
if let Some(&expr) = global_scope.get(&sym) { if let Some(expr) = global_scope.get(&sym) {
return Ok(expr); let ir = match expr {
Ir::Builtins => Ir::Builtins,
Ir::Builtin(s) => Ir::Builtin(*s),
Ir::Bool(b) => Ir::Bool(*b),
Ir::Null => Ir::Null,
_ => unreachable!("globals should only contain leaf IR nodes"),
};
return Ok(self.new_expr(ir));
} }
} }
&Scope::Repl(repl_bindings) => { &Scope::Repl(repl_bindings) => {
if repl_bindings.contains(&sym) { if repl_bindings.contains(&sym) {
return Ok(self.new_expr(ReplBinding { inner: sym, span }.to_ir())); return Ok(self.new_expr(Ir::ReplBinding(sym)));
} }
} }
Scope::ScopedImport(scoped_bindings) => { Scope::ScopedImport(scoped_bindings) => {
if scoped_bindings.contains(&sym) { if scoped_bindings.contains(&sym) {
return Ok(self.new_expr(ScopedImportBinding { inner: sym, span }.to_ir())); return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
} }
} }
Scope::Let(let_scope) => { Scope::Let(let_scope) => {
if let Some(&expr) = let_scope.get(&sym) { if let Some(&expr) = let_scope.get(&sym) {
return Ok(self.new_expr(Thunk { inner: expr, span }.to_ir())); return Ok(self.new_expr(Ir::Thunk(expr)));
} }
} }
&Scope::Param(param_sym, expr) => { &Scope::Param(param_sym, id) => {
if param_sym == sym { if param_sym == sym {
return Ok(expr); return Ok(self.new_expr(Ir::Arg(id)));
} }
} }
} }
} }
if self.with_scope_count > 0 { if self.with_scope_count > 0 {
Ok(self.new_expr(WithLookup { inner: sym, span }.to_ir())) Ok(self.new_expr(Ir::WithLookup(sym)))
} else { } else {
Err(Error::downgrade_error( Err(Error::downgrade_error(
format!("'{}' not found", self.get_sym(sym)), format!("'{}' not found", self.get_sym(sym)),
@@ -558,49 +712,37 @@ impl DowngradeContext for DowngradeCtx<'_> {
} }
} }
fn replace_ir(&mut self, id: ExprId, expr: Ir) {
let local_id = id.0 - self.ctx.irs.len();
*self.irs.get_mut(local_id).expect("ExprId out of bounds") = expr;
}
fn get_current_source(&self) -> Source { fn get_current_source(&self) -> Source {
self.ctx.get_current_source() self.source.clone()
} }
#[allow(refining_impl_trait)] fn with_let_scope<F, R>(&mut self, keys: &[SymId], f: F) -> Result<R>
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
let start = self.ctx.irs.len() + self.irs.len();
let range = (start..start + slots).map(ExprId);
let span = rnix::TextRange::default();
// Fill reserved slots with placeholder value
self.irs.extend(
range
.clone()
.map(|slot| Thunk { inner: slot, span }.to_ir()),
);
range
}
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
use crate::ir::TopLevel;
let body = root.downgrade(&mut self)?;
let thunks = self.thunk_scopes.pop().expect("no thunk scope left???");
let span = self.get_ir(body).span();
let top_level = self.new_expr(TopLevel { body, thunks, span }.to_ir());
self.ctx.irs.extend(self.irs);
Ok(top_level)
}
fn with_let_scope<F, R>(&mut self, bindings: HashMap<SymId, ExprId>, f: F) -> R
where where
F: FnOnce(&mut Self) -> R, F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
{ {
self.scopes.push(Scope::Let(bindings)); let base = *self.thunk_count;
let mut guard = ScopeGuard { ctx: self }; *self.thunk_count = self
f(guard.as_ctx()) .thunk_count
.checked_add(keys.len())
.expect("thunk id overflow");
let iter = keys.iter().enumerate().map(|(offset, &key)| {
(
key,
ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }),
)
});
self.scopes.push(Scope::Let(iter.collect()));
let (vals, ret) = {
let mut guard = ScopeGuard { ctx: self };
f(guard.as_ctx())?
};
assert_eq!(keys.len(), vals.len());
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
Ok(ret)
} }
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ExprId, f: F) -> R fn with_param_scope<F, R>(&mut self, param: SymId, arg: ArgId, f: F) -> R
where where
F: FnOnce(&mut Self) -> R, F: FnOnce(&mut Self) -> R,
{ {
@@ -619,22 +761,41 @@ impl DowngradeContext for DowngradeCtx<'_> {
ret ret
} }
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<(ExprId, ExprId)>) fn with_thunk_scope<F, R>(
&mut self,
f: F,
) -> (
R,
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
)
where where
F: FnOnce(&mut Self) -> R, F: FnOnce(&mut Self) -> R,
{ {
self.thunk_scopes.push(Vec::new()); self.thunk_scopes.push(ThunkScope::new_in(self.bump));
let ret = f(self); let ret = f(self);
( (
ret, ret,
self.thunk_scopes.pop().expect("no thunk scope left???"), self.thunk_scopes
.pop()
.expect("no thunk scope left???")
.bindings,
) )
} }
fn register_thunk(&mut self, slot: ExprId, inner: ExprId) { fn bump(&self) -> &'ir bumpalo::Bump {
self.thunk_scopes self.bump
.last_mut() }
.expect("register_thunk without active scope") }
.push((slot, inner));
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
let body = root.downgrade(&mut self)?;
let thunks = self
.thunk_scopes
.pop()
.expect("no thunk scope left???")
.bindings;
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
Ok(ir.freeze(self.token))
} }
} }

354
nix-js/src/disassembler.rs Normal file
View File

@@ -0,0 +1,354 @@
use std::fmt::Write;
use colored::Colorize;
use num_enum::TryFromPrimitive;
use crate::bytecode::{Bytecode, Constant, Op};
pub(crate) trait DisassemblerContext {
fn lookup_string(&self, id: u32) -> &str;
fn lookup_constant(&self, id: u32) -> &Constant;
}
pub(crate) struct Disassembler<'a, Ctx> {
code: &'a [u8],
ctx: &'a Ctx,
pos: usize,
}
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
pub fn new(bytecode: &'a Bytecode, ctx: &'a Ctx) -> Self {
Self {
code: &bytecode.code,
ctx,
pos: 0,
}
}
fn read_u8(&mut self) -> u8 {
let b = self.code[self.pos];
self.pos += 1;
b
}
fn read_u16(&mut self) -> u16 {
let bytes = self.code[self.pos..self.pos + 2]
.try_into()
.expect("no enough bytes");
self.pos += 2;
u16::from_le_bytes(bytes)
}
fn read_u32(&mut self) -> u32 {
let bytes = self.code[self.pos..self.pos + 4]
.try_into()
.expect("no enough bytes");
self.pos += 4;
u32::from_le_bytes(bytes)
}
fn read_i32(&mut self) -> i32 {
let bytes = self.code[self.pos..self.pos + 4]
.try_into()
.expect("no enough bytes");
self.pos += 4;
i32::from_le_bytes(bytes)
}
pub fn disassemble(&mut self) -> String {
self.disassemble_impl(false)
}
pub fn disassemble_colored(&mut self) -> String {
self.disassemble_impl(true)
}
fn disassemble_impl(&mut self, color: bool) -> String {
let mut out = String::new();
if color {
let _ = writeln!(out, "{}", "=== Bytecode Disassembly ===".bold().white());
let _ = writeln!(
out,
"{} {}",
"Length:".white(),
format!("{} bytes", self.code.len()).cyan()
);
} else {
let _ = writeln!(out, "=== Bytecode Disassembly ===");
let _ = writeln!(out, "Length: {} bytes", self.code.len());
}
while self.pos < self.code.len() {
let start_pos = self.pos;
let op_byte = self.read_u8();
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
let bytes_slice = &self.code[start_pos + 1..self.pos];
for (i, chunk) in bytes_slice.chunks(4).enumerate() {
let bytes_str = {
let mut temp = String::new();
if i == 0 {
let _ = write!(&mut temp, "{:02x}", self.code[start_pos]);
} else {
let _ = write!(&mut temp, " ");
}
for b in chunk.iter() {
let _ = write!(&mut temp, " {:02x}", b);
}
temp
};
if i == 0 {
if color {
let sep = if args.is_empty() { "" } else { " " };
let _ = writeln!(
out,
"{} {:<14} | {}{}{}",
format!("{:04x}", start_pos).dimmed(),
bytes_str.green(),
mnemonic.yellow().bold(),
sep,
args.cyan()
);
} else {
let op_str = if args.is_empty() {
mnemonic.to_string()
} else {
format!("{} {}", mnemonic, args)
};
let _ = writeln!(out, "{:04x} {:<14} | {}", start_pos, bytes_str, op_str);
}
} else {
let extra_width = start_pos.ilog2() >> 4;
if color {
let _ = write!(out, " ");
for _ in 0..extra_width {
let _ = write!(out, " ");
}
let _ = writeln!(out, " {:<14} |", bytes_str.green());
} else {
let _ = write!(out, " ");
for _ in 0..extra_width {
let _ = write!(out, " ");
}
let _ = writeln!(out, " {:<14} |", bytes_str);
}
}
}
}
out
}
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
match op {
Op::PushConst => {
let idx = self.read_u32();
let val = self.ctx.lookup_constant(idx);
let val_str = match val {
Constant::Int(i) => format!("Int({})", i),
Constant::Float(f) => format!("Float(bits: {})", f),
};
("PushConst", format!("@{} ({})", idx, val_str))
}
Op::PushString => {
let idx = self.read_u32();
let s = self.ctx.lookup_string(idx);
let len = s.len();
let mut s_fmt = format!("{:?}", s);
if s_fmt.len() > 60 {
s_fmt.truncate(57);
#[allow(clippy::unwrap_used)]
write!(s_fmt, "...\" (total {len} bytes)").unwrap();
}
("PushString", format!("@{} {}", idx, s_fmt))
}
Op::PushNull => ("PushNull", String::new()),
Op::PushTrue => ("PushTrue", String::new()),
Op::PushFalse => ("PushFalse", String::new()),
Op::LoadLocal => {
let idx = self.read_u32();
("LoadLocal", format!("[{}]", idx))
}
Op::LoadOuter => {
let depth = self.read_u8();
let idx = self.read_u32();
("LoadOuter", format!("depth={} [{}]", depth, idx))
}
Op::StoreLocal => {
let idx = self.read_u32();
("StoreLocal", format!("[{}]", idx))
}
Op::AllocLocals => {
let count = self.read_u32();
("AllocLocals", format!("count={}", count))
}
Op::MakeThunk => {
let offset = self.read_u32();
let label_idx = self.read_u32();
let label = self.ctx.lookup_string(label_idx);
("MakeThunk", format!("-> {:04x} label={}", offset, label))
}
Op::MakeClosure => {
let offset = self.read_u32();
let slots = self.read_u32();
("MakeClosure", format!("-> {:04x} slots={}", offset, slots))
}
Op::MakePatternClosure => {
let offset = self.read_u32();
let slots = self.read_u32();
let req_count = self.read_u16();
let opt_count = self.read_u16();
let ellipsis = self.read_u8() != 0;
let mut arg_str = format!(
"-> {:04x} slots={} req={} opt={} ...={})",
offset, slots, req_count, opt_count, ellipsis
);
arg_str.push_str(" Args=[");
for _ in 0..req_count {
let idx = self.read_u32();
arg_str.push_str(&format!("Req({}) ", self.ctx.lookup_string(idx)));
}
for _ in 0..opt_count {
let idx = self.read_u32();
arg_str.push_str(&format!("Opt({}) ", self.ctx.lookup_string(idx)));
}
let total_args = req_count + opt_count;
for _ in 0..total_args {
let _name_idx = self.read_u32();
let _span_id = self.read_u32();
}
arg_str.push(']');
("MakePatternClosure", arg_str)
}
Op::Call => {
let span_id = self.read_u32();
("Call", format!("span={}", span_id))
}
Op::CallNoSpan => ("CallNoSpan", String::new()),
Op::MakeAttrs => {
let count = self.read_u32();
("MakeAttrs", format!("size={}", count))
}
Op::MakeAttrsDyn => {
let static_count = self.read_u32();
let dyn_count = self.read_u32();
(
"MakeAttrsDyn",
format!("static={} dyn={}", static_count, dyn_count),
)
}
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
Op::Select => {
let path_len = self.read_u16();
let span_id = self.read_u32();
("Select", format!("path_len={} span={}", path_len, span_id))
}
Op::SelectDefault => {
let path_len = self.read_u16();
let span_id = self.read_u32();
(
"SelectDefault",
format!("path_len={} span={}", path_len, span_id),
)
}
Op::HasAttr => {
let path_len = self.read_u16();
("HasAttr", format!("path_len={}", path_len))
}
Op::MakeList => {
let count = self.read_u32();
("MakeList", format!("size={}", count))
}
Op::OpAdd => ("OpAdd", String::new()),
Op::OpSub => ("OpSub", String::new()),
Op::OpMul => ("OpMul", String::new()),
Op::OpDiv => ("OpDiv", String::new()),
Op::OpEq => ("OpEq", String::new()),
Op::OpNeq => ("OpNeq", String::new()),
Op::OpLt => ("OpLt", String::new()),
Op::OpGt => ("OpGt", String::new()),
Op::OpLeq => ("OpLeq", String::new()),
Op::OpGeq => ("OpGeq", String::new()),
Op::OpConcat => ("OpConcat", String::new()),
Op::OpUpdate => ("OpUpdate", String::new()),
Op::OpNeg => ("OpNeg", String::new()),
Op::OpNot => ("OpNot", String::new()),
Op::ForceBool => ("ForceBool", String::new()),
Op::JumpIfFalse => {
let offset = self.read_i32();
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
(
"JumpIfFalse",
format!("-> {:04x} offset={}", target, offset),
)
}
Op::JumpIfTrue => {
let offset = self.read_i32();
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
("JumpIfTrue", format!("-> {:04x} offset={}", target, offset))
}
Op::Jump => {
let offset = self.read_i32();
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
("Jump", format!("-> {:04x} offset={}", target, offset))
}
Op::ConcatStrings => {
let count = self.read_u16();
let force = self.read_u8();
("ConcatStrings", format!("count={} force={}", count, force))
}
Op::ResolvePath => ("ResolvePath", String::new()),
Op::Assert => {
let raw_idx = self.read_u32();
let span_id = self.read_u32();
("Assert", format!("text_id={} span={}", raw_idx, span_id))
}
Op::PushWith => ("PushWith", String::new()),
Op::PopWith => ("PopWith", String::new()),
Op::WithLookup => {
let idx = self.read_u32();
let name = self.ctx.lookup_string(idx);
("WithLookup", format!("{:?}", name))
}
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
Op::LoadBuiltin => {
let idx = self.read_u32();
let name = self.ctx.lookup_string(idx);
("LoadBuiltin", format!("{:?}", name))
}
Op::MkPos => {
let span_id = self.read_u32();
("MkPos", format!("id={}", span_id))
}
Op::LoadReplBinding => {
let idx = self.read_u32();
let name = self.ctx.lookup_string(idx);
("LoadReplBinding", format!("{:?}", name))
}
Op::LoadScopedBinding => {
let idx = self.read_u32();
let name = self.ctx.lookup_string(idx);
("LoadScopedBinding", format!("{:?}", name))
}
Op::Return => ("Return", String::new()),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
use reqwest::blocking::Client;
use std::time::Duration; use std::time::Duration;
use reqwest::blocking::Client;
pub struct Downloader { pub struct Downloader {
client: Client, client: Client,
} }

View File

@@ -1,9 +1,10 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use rusqlite::{Connection, OptionalExtension, params}; use rusqlite::{Connection, OptionalExtension, params};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug)] #[derive(Debug)]
pub enum CacheError { pub enum CacheError {

View File

@@ -1,46 +1,158 @@
use derive_more::{IsVariant, TryUnwrap, Unwrap}; use std::{
use hashbrown::HashMap; hash::{Hash, Hasher},
ops::Deref,
};
use bumpalo::{Bump, boxed::Box, collections::Vec};
use ghost_cell::{GhostCell, GhostToken};
use rnix::{TextRange, ast}; use rnix::{TextRange, ast};
use string_interner::symbol::SymbolU32; use string_interner::symbol::SymbolU32;
use nix_js_macros::ir; pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
ir! { #[repr(transparent)]
Ir, #[derive(Clone, Copy)]
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>);
impl<'id, 'ir> IrRef<'id, 'ir> {
pub fn new(ir: &'ir GhostCell<'id, Ir<'ir, Self>>) -> Self {
Self(ir)
}
pub fn alloc(bump: &'ir Bump, ir: Ir<'ir, Self>) -> Self {
Self(bump.alloc(GhostCell::new(ir)))
}
/// Freeze a mutable IR reference into a read-only one, consuming the
/// `GhostToken` to prevent any further mutation.
///
/// # Safety
/// The transmute is sound because:
/// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`
/// - `IrRef<'id, 'ir>` is `#[repr(transparent)]` over
/// `&'ir GhostCell<'id, Ir<'ir, Self>>`
/// - `RawIrRef<'ir>` is `#[repr(transparent)]` over `&'ir Ir<'ir, Self>`
/// - `Ir<'ir, Ref>` is `#[repr(C)]` and both ref types are pointer-sized
///
/// Consuming the `GhostToken` guarantees no `borrow_mut` calls can occur
/// afterwards, so the shared `&Ir` references from `RawIrRef::Deref` can
/// never alias with mutable references.
pub fn freeze(self, _token: GhostToken<'id>) -> RawIrRef<'ir> {
unsafe { std::mem::transmute(self) }
}
}
impl<'id, 'ir> Deref for IrRef<'id, 'ir> {
type Target = GhostCell<'id, Ir<'ir, IrRef<'id, 'ir>>>;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
impl<'ir> Deref for RawIrRef<'ir> {
type Target = Ir<'ir, RawIrRef<'ir>>;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[repr(C)]
pub enum Ir<'ir, Ref> {
Int(i64), Int(i64),
Float(f64), Float(f64),
Bool(bool), Bool(bool),
Null, Null,
Str { pub val: String }, Str(Box<'ir, String>),
AttrSet { pub stcs: HashMap<SymId, (ExprId, rnix::TextRange)>, pub dyns: Vec<(ExprId, ExprId, rnix::TextRange)> }, AttrSet {
List { pub items: Vec<ExprId> }, stcs: HashMap<'ir, SymId, (Ref, TextRange)>,
dyns: Vec<'ir, (Ref, Ref, TextRange)>,
},
List {
items: Vec<'ir, Ref>,
},
Path(Ref),
ConcatStrings {
parts: Vec<'ir, Ref>,
force_string: bool,
},
HasAttr { pub lhs: ExprId, pub rhs: Vec<Attr> }, // OPs
BinOp { pub lhs: ExprId, pub rhs: ExprId, pub kind: BinOpKind }, UnOp {
UnOp { pub rhs: ExprId, pub kind: UnOpKind }, rhs: Ref,
Select { pub expr: ExprId, pub attrpath: Vec<Attr>, pub default: Option<ExprId> }, kind: UnOpKind,
If { pub cond: ExprId, pub consq: ExprId, pub alter: ExprId }, },
Call { pub func: ExprId, pub arg: ExprId }, BinOp {
Assert { pub assertion: ExprId, pub expr: ExprId, pub assertion_raw: String }, lhs: Ref,
ConcatStrings { pub parts: Vec<ExprId>, pub force_string: bool }, rhs: Ref,
Path { pub expr: ExprId }, kind: BinOpKind,
Func { pub body: ExprId, pub param: Option<Param>, pub arg: ExprId, pub thunks: Vec<(ExprId, ExprId)> }, },
TopLevel { pub body: ExprId, pub thunks: Vec<(ExprId, ExprId)> }, HasAttr {
lhs: Ref,
rhs: Vec<'ir, Attr<Ref>>,
},
Select {
expr: Ref,
attrpath: Vec<'ir, Attr<Ref>>,
default: Option<Ref>,
span: TextRange,
},
// Conditionals
If {
cond: Ref,
consq: Ref,
alter: Ref,
},
Assert {
assertion: Ref,
expr: Ref,
assertion_raw: String,
span: TextRange,
},
With {
namespace: Ref,
body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
WithLookup(SymId),
// Function related
Func {
body: Ref,
param: Option<Param<'ir>>,
arg: ArgId,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
Arg(ArgId), Arg(ArgId),
Thunk(ExprId), Call {
func: Ref,
arg: Ref,
span: TextRange,
},
// Builtins
Builtins, Builtins,
Builtin(SymId), Builtin(SymId),
CurPos,
// Misc
TopLevel {
body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
Thunk(ThunkId),
CurPos(TextRange),
ReplBinding(SymId), ReplBinding(SymId),
ScopedImportBinding(SymId), ScopedImportBinding(SymId),
WithExpr { pub namespace: ExprId, pub body: ExprId, pub thunks: Vec<(ExprId, ExprId)> },
WithLookup(SymId),
} }
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ExprId(pub usize); pub struct ThunkId(pub usize);
pub type SymId = SymbolU32; pub type SymId = SymbolU32;
@@ -50,18 +162,18 @@ pub struct ArgId(pub usize);
/// Represents a key in an attribute path. /// Represents a key in an attribute path.
#[allow(unused)] #[allow(unused)]
#[derive(Debug, TryUnwrap)] #[derive(Debug)]
pub enum Attr { pub enum Attr<Ref> {
/// A dynamic attribute key, which is an expression that must evaluate to a string. /// A dynamic attribute key, which is an expression that must evaluate to a string.
/// Example: `attrs.${key}` /// Example: `attrs.${key}`
Dynamic(ExprId, TextRange), Dynamic(Ref, TextRange),
/// A static attribute key. /// A static attribute key.
/// Example: `attrs.key` /// Example: `attrs.key`
Str(SymId, TextRange), Str(SymId, TextRange),
} }
/// The kinds of binary operations supported in Nix. /// The kinds of binary operations supported in Nix.
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum BinOpKind { pub enum BinOpKind {
// Arithmetic // Arithmetic
Add, Add,
@@ -118,7 +230,7 @@ impl From<ast::BinOpKind> for BinOpKind {
} }
/// The kinds of unary operations. /// The kinds of unary operations.
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum UnOpKind { pub enum UnOpKind {
Neg, // Negation (`-`) Neg, // Negation (`-`)
Not, // Logical not (`!`) Not, // Logical not (`!`)
@@ -135,8 +247,437 @@ impl From<ast::UnaryOpKind> for UnOpKind {
/// Describes the parameters of a function. /// Describes the parameters of a function.
#[derive(Debug)] #[derive(Debug)]
pub struct Param { pub struct Param<'ir> {
pub required: Vec<(SymId, TextRange)>, pub required: Vec<'ir, (SymId, TextRange)>,
pub optional: Vec<(SymId, TextRange)>, pub optional: Vec<'ir, (SymId, 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::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,
}
}

View File

@@ -5,8 +5,10 @@ pub mod error;
pub mod logging; pub mod logging;
pub mod value; pub mod value;
mod bytecode;
mod codegen; mod codegen;
mod derivation; mod derivation;
mod disassembler;
mod downgrade; mod downgrade;
mod fetcher; mod fetcher;
mod ir; mod ir;

View File

@@ -1,5 +1,6 @@
use std::env; use std::env;
use std::io::IsTerminal; use std::io::IsTerminal;
use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt};
pub fn init_logging() { pub fn init_logging() {

View File

@@ -30,7 +30,7 @@ enum Command {
#[clap(flatten)] #[clap(flatten)]
source: ExprSource, source: ExprSource,
#[arg(long)] #[arg(long)]
silent: bool silent: bool,
}, },
Eval { Eval {
#[clap(flatten)] #[clap(flatten)]
@@ -77,10 +77,10 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
} else { } else {
unreachable!() unreachable!()
}; };
match context.compile(src) { match context.compile_bytecode(src) {
Ok(compiled) => { Ok(compiled) => {
if !silent { if !silent {
println!("{compiled}"); println!("{}", context.disassemble_colored(&compiled));
} }
} }
Err(err) => { Err(err) => {
@@ -101,9 +101,9 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
} else { } else {
unreachable!() unreachable!()
}; };
match context.eval_shallow(src) { match context.eval_deep(src) {
Ok(value) => { Ok(value) => {
println!("{value}"); println!("{}", value.display_compat());
} }
Err(err) => { Err(err) => {
eprintln!("{:?}", miette::Report::new(*err)); eprintln!("{:?}", miette::Report::new(*err));
@@ -180,7 +180,7 @@ fn main() -> Result<()> {
)?; )?;
match cli.command { match cli.command {
Command::Compile { source , silent } => run_compile(&mut context, source, silent), Command::Compile { source, silent } => run_compile(&mut context, source, silent),
Command::Eval { source } => run_eval(&mut context, source), Command::Eval { source } => run_eval(&mut context, source),
Command::Repl => run_repl(&mut context), Command::Repl => run_repl(&mut context),
} }

View File

@@ -1,8 +1,9 @@
use nix_nar::Encoder;
use sha2::{Digest, Sha256};
use std::io::Read; use std::io::Read;
use std::path::Path; use std::path::Path;
use nix_nar::Encoder;
use sha2::{Digest, Sha256};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
pub fn compute_nar_hash(path: &Path) -> Result<[u8; 32]> { pub fn compute_nar_hash(path: &Path) -> Result<[u8; 32]> {
@@ -27,11 +28,13 @@ pub fn pack_nar(path: &Path) -> Result<Vec<u8>> {
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod tests { mod tests {
use super::*;
use std::fs; use std::fs;
use tempfile::TempDir; use tempfile::TempDir;
#[test] use super::*;
#[test_log::test]
fn test_simple_file() { fn test_simple_file() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();
let file_path = temp.path().join("test.txt"); let file_path = temp.path().join("test.txt");
@@ -46,7 +49,7 @@ mod tests {
assert_eq!(hash.len(), 64); assert_eq!(hash.len(), 64);
} }
#[test] #[test_log::test]
fn test_directory() { fn test_directory() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();
fs::write(temp.path().join("a.txt"), "aaa").unwrap(); fs::write(temp.path().join("a.txt"), "aaa").unwrap();

View File

@@ -6,6 +6,7 @@ use std::path::Path;
use deno_core::PollEventLoopOptions; use deno_core::PollEventLoopOptions;
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8}; use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
use crate::bytecode::{Bytecode, Constant};
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::store::DaemonStore; use crate::store::DaemonStore;
use crate::value::{AttrSet, List, Symbol, Value}; use crate::value::{AttrSet, List, Symbol, Value};
@@ -14,6 +15,7 @@ use crate::value::{AttrSet, List, Symbol, Value};
pub(crate) mod inspector; pub(crate) mod inspector;
mod ops; mod ops;
use ops::*; use ops::*;
mod value;
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>; type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
type LocalValue<'a> = v8::Local<'a, v8::Value>; type LocalValue<'a> = v8::Local<'a, v8::Value>;
@@ -24,9 +26,12 @@ pub(crate) trait RuntimeContext: 'static {
fn add_source(&mut self, path: Source); fn add_source(&mut self, path: Source);
fn compile(&mut self, source: Source) -> Result<String>; fn compile(&mut self, source: Source) -> Result<String>;
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>; fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>;
fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode>;
fn compile_bytecode_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<Bytecode>;
fn get_source(&self, id: usize) -> Source; fn get_source(&self, id: usize) -> Source;
fn get_store(&self) -> &DaemonStore; fn get_store(&self) -> &DaemonStore;
fn get_span(&self, id: usize) -> (usize, rnix::TextRange); fn get_span(&self, id: usize) -> (usize, rnix::TextRange);
fn get_unsynced(&mut self) -> (&[String], &[Constant], usize, usize);
} }
pub(crate) trait OpStateExt<Ctx: RuntimeContext> { pub(crate) trait OpStateExt<Ctx: RuntimeContext> {
@@ -91,7 +96,7 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
mod private { mod private {
use deno_error::js_error_wrapper; use deno_error::js_error_wrapper;
#[derive(Debug)] #[derive(Debug)]
pub struct SimpleErrorWrapper(pub(crate) String); pub struct SimpleErrorWrapper(String);
impl std::fmt::Display for SimpleErrorWrapper { impl std::fmt::Display for SimpleErrorWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f) std::fmt::Display::fmt(&self.0, f)
@@ -120,11 +125,8 @@ pub(crate) struct Runtime<Ctx: RuntimeContext> {
rt: tokio::runtime::Runtime, rt: tokio::runtime::Runtime,
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
wait_for_inspector: bool, wait_for_inspector: bool,
is_thunk_symbol: v8::Global<v8::Symbol>, symbols: GlobalSymbols,
primop_metadata_symbol: v8::Global<v8::Symbol>, cached_fns: CachedFunctions,
has_context_symbol: v8::Global<v8::Symbol>,
is_path_symbol: v8::Global<v8::Symbol>,
is_cycle_symbol: v8::Global<v8::Symbol>,
_marker: PhantomData<Ctx>, _marker: PhantomData<Ctx>,
} }
@@ -166,15 +168,11 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
js_runtime.op_state().borrow_mut().put(RegexCache::new()); js_runtime.op_state().borrow_mut().put(RegexCache::new());
js_runtime.op_state().borrow_mut().put(DrvHashCache::new()); js_runtime.op_state().borrow_mut().put(DrvHashCache::new());
let ( let (symbols, cached_fns) = {
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
) = {
deno_core::scope!(scope, &mut js_runtime); deno_core::scope!(scope, &mut js_runtime);
Self::get_symbols(scope)? let symbols = Self::get_symbols(scope)?;
let cached_fns = Self::get_cached_functions(scope)?;
(symbols, cached_fns)
}; };
Ok(Self { Ok(Self {
@@ -186,11 +184,8 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
.expect("failed to build tokio runtime"), .expect("failed to build tokio runtime"),
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
wait_for_inspector: inspector_options.wait, wait_for_inspector: inspector_options.wait,
is_thunk_symbol, symbols,
primop_metadata_symbol, cached_fns,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
_marker: PhantomData, _marker: PhantomData,
}) })
} }
@@ -236,34 +231,93 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
// Retrieve scope from JsRuntime // Retrieve scope from JsRuntime
deno_core::scope!(scope, self.js_runtime); deno_core::scope!(scope, self.js_runtime);
let local_value = v8::Local::new(scope, &global_value); let local_value = v8::Local::new(scope, &global_value);
let is_thunk_symbol = v8::Local::new(scope, &self.is_thunk_symbol); let symbols = &self.symbols.local(scope);
let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol);
let has_context_symbol = v8::Local::new(scope, &self.has_context_symbol);
let is_path_symbol = v8::Local::new(scope, &self.is_path_symbol);
let is_cycle_symbol = v8::Local::new(scope, &self.is_cycle_symbol);
Ok(to_value( Ok(to_value(local_value, scope, symbols))
local_value,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
))
} }
/// get (IS_THUNK, PRIMOP_METADATA, HAS_CONTEXT, IS_PATH, IS_CYCLE) pub(crate) fn eval_bytecode(
#[allow(clippy::type_complexity)] &mut self,
fn get_symbols( result: Bytecode,
scope: &ScopeRef, ctx: &mut Ctx,
) -> Result<( force_mode: ForceMode,
v8::Global<v8::Symbol>, ) -> Result<Value> {
v8::Global<v8::Symbol>, let ctx: &'static mut Ctx = unsafe { &mut *(ctx as *mut Ctx) };
v8::Global<v8::Symbol>, {
v8::Global<v8::Symbol>, deno_core::scope!(scope, self.js_runtime);
v8::Global<v8::Symbol>, sync_global_tables(scope, &self.cached_fns, ctx);
)> { }
let op_state = self.js_runtime.op_state();
op_state.borrow_mut().put(ctx);
#[cfg(feature = "inspector")]
if self.wait_for_inspector {
self.js_runtime
.inspector()
.wait_for_session_and_break_on_next_statement();
} else {
self.js_runtime.inspector().wait_for_session();
}
deno_core::scope!(scope, self.js_runtime);
let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(result.code);
let ab = v8::ArrayBuffer::with_backing_store(scope, &store.make_shared());
let u8a = v8::Uint8Array::new(scope, ab, 0, ab.byte_length())
.ok_or_else(|| Error::internal("failed to create Uint8Array".into()))?;
let dir = v8::String::new(scope, &result.current_dir)
.ok_or_else(|| Error::internal("failed to create dir string".into()))?;
let undef = v8::undefined(scope);
let tc = std::pin::pin!(v8::TryCatch::new(scope));
let scope = &mut tc.init();
let exec_bytecode = v8::Local::new(scope, &self.cached_fns.exec_bytecode);
let raw_result = exec_bytecode
.call(scope, undef.into(), &[u8a.into(), dir.into()])
.ok_or_else(|| {
scope
.exception()
.map(|e| {
let op_state_borrow = op_state.borrow();
let ctx: &Ctx = op_state_borrow.get_ctx();
Box::new(crate::error::parse_js_error(
deno_core::error::JsError::from_v8_exception(scope, e),
ctx,
))
})
.unwrap_or_else(|| Error::internal("bytecode execution failed".into()))
})?;
let force_fn = match force_mode {
ForceMode::Force => &self.cached_fns.force_fn,
ForceMode::ForceShallow => &self.cached_fns.force_shallow_fn,
ForceMode::ForceDeep => &self.cached_fns.force_deep_fn,
};
let force_fn = v8::Local::new(scope, force_fn);
let forced = force_fn
.call(scope, undef.into(), &[raw_result])
.ok_or_else(|| {
scope
.exception()
.map(|e| {
let op_state_borrow = op_state.borrow();
let ctx: &Ctx = op_state_borrow.get_ctx();
Box::new(crate::error::parse_js_error(
deno_core::error::JsError::from_v8_exception(scope, e),
ctx,
))
})
.unwrap_or_else(|| Error::internal("force failed".into()))
})?;
let symbols = &self.symbols.local(scope);
Ok(to_value(forced, scope, symbols))
}
fn get_symbols(scope: &ScopeRef) -> Result<GlobalSymbols> {
let global = scope.get_current_context().global(scope); let global = scope.get_current_context().global(scope);
let nix_key = v8::String::new(scope, "Nix") let nix_key = v8::String::new(scope, "Nix")
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?; .ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
@@ -295,18 +349,148 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
let is_path = get_symbol("IS_PATH")?; let is_path = get_symbol("IS_PATH")?;
let is_cycle = get_symbol("IS_CYCLE")?; let is_cycle = get_symbol("IS_CYCLE")?;
Ok((is_thunk, primop_metadata, has_context, is_path, is_cycle)) Ok(GlobalSymbols {
is_thunk,
primop_metadata,
has_context,
is_path,
is_cycle,
})
}
fn get_cached_functions(scope: &ScopeRef) -> Result<CachedFunctions> {
let global = scope.get_current_context().global(scope);
let nix_key = v8::String::new(scope, "Nix")
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
let nix_obj = global
.get(scope, nix_key.into())
.ok_or_else(|| Error::internal("failed to get global Nix object".into()))?
.to_object(scope)
.ok_or_else(|| {
Error::internal("failed to convert global Nix Value to object".into())
})?;
let get_fn = |name: &str| -> Result<v8::Global<v8::Function>> {
let key = v8::String::new(scope, name)
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
let val = nix_obj
.get(scope, key.into())
.ok_or_else(|| Error::internal(format!("failed to get Nix.{name}")))?;
let func = val
.try_cast::<v8::Function>()
.map_err(|err| Error::internal(format!("Nix.{name} is not a function ({err})")))?;
Ok(v8::Global::new(scope, func))
};
let exec_bytecode = get_fn("execBytecode")?;
let force_fn = get_fn("force")?;
let force_shallow_fn = get_fn("forceShallow")?;
let force_deep_fn = get_fn("forceDeep")?;
let strings_key = v8::String::new(scope, "strings")
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
let strings_array = nix_obj
.get(scope, strings_key.into())
.ok_or_else(|| Error::internal("failed to get Nix.strings".into()))?
.try_cast::<v8::Array>()
.map_err(|err| Error::internal(format!("Nix.strings is not an array ({err})")))?;
let constants_key = v8::String::new(scope, "constants")
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
let constants_array = nix_obj
.get(scope, constants_key.into())
.ok_or_else(|| Error::internal("failed to get Nix.constants".into()))?
.try_cast::<v8::Array>()
.map_err(|err| Error::internal(format!("Nix.constants is not an array ({err})")))?;
Ok(CachedFunctions {
exec_bytecode,
force_fn,
force_shallow_fn,
force_deep_fn,
strings_array: v8::Global::new(scope, strings_array),
constants_array: v8::Global::new(scope, constants_array),
})
}
}
struct GlobalSymbols {
is_thunk: v8::Global<v8::Symbol>,
primop_metadata: v8::Global<v8::Symbol>,
has_context: v8::Global<v8::Symbol>,
is_path: v8::Global<v8::Symbol>,
is_cycle: v8::Global<v8::Symbol>,
}
impl GlobalSymbols {
fn local<'a>(&self, scope: &ScopeRef<'a, '_>) -> LocalSymbols<'a> {
LocalSymbols {
is_thunk: v8::Local::new(scope, &self.is_thunk),
primop_metadata: v8::Local::new(scope, &self.primop_metadata),
has_context: v8::Local::new(scope, &self.has_context),
is_path: v8::Local::new(scope, &self.is_path),
is_cycle: v8::Local::new(scope, &self.is_cycle),
}
}
}
struct LocalSymbols<'a> {
is_thunk: v8::Local<'a, v8::Symbol>,
primop_metadata: v8::Local<'a, v8::Symbol>,
has_context: v8::Local<'a, v8::Symbol>,
is_path: v8::Local<'a, v8::Symbol>,
is_cycle: v8::Local<'a, v8::Symbol>,
}
struct CachedFunctions {
exec_bytecode: v8::Global<v8::Function>,
force_fn: v8::Global<v8::Function>,
force_shallow_fn: v8::Global<v8::Function>,
force_deep_fn: v8::Global<v8::Function>,
strings_array: v8::Global<v8::Array>,
constants_array: v8::Global<v8::Array>,
}
pub(crate) enum ForceMode {
Force,
ForceShallow,
ForceDeep,
}
fn sync_global_tables<Ctx: RuntimeContext>(
scope: &ScopeRef,
cached: &CachedFunctions,
ctx: &mut Ctx,
) {
let (new_strings, new_constants, strings_base, constants_base) = ctx.get_unsynced();
if !new_strings.is_empty() {
let s_array = v8::Local::new(scope, &cached.strings_array);
for (i, s) in new_strings.iter().enumerate() {
let idx = (strings_base + i) as u32;
#[allow(clippy::unwrap_used)]
let val = v8::String::new(scope, s).unwrap();
s_array.set_index(scope, idx, val.into());
}
}
if !new_constants.is_empty() {
let k_array = v8::Local::new(scope, &cached.constants_array);
for (i, c) in new_constants.iter().enumerate() {
let idx = (constants_base + i) as u32;
let val: v8::Local<v8::Value> = match c {
Constant::Int(n) => v8::BigInt::new_from_i64(scope, *n).into(),
Constant::Float(bits) => v8::Number::new(scope, f64::from_bits(*bits)).into(),
};
k_array.set_index(scope, idx, val);
}
} }
} }
fn to_value<'a>( fn to_value<'a>(
val: LocalValue<'a>, val: LocalValue<'a>,
scope: &ScopeRef<'a, '_>, scope: &ScopeRef<'a, '_>,
is_thunk_symbol: LocalSymbol<'a>, symbols: &LocalSymbols<'a>,
primop_metadata_symbol: LocalSymbol<'a>,
has_context_symbol: LocalSymbol<'a>,
is_path_symbol: LocalSymbol<'a>,
is_cycle_symbol: LocalSymbol<'a>,
) -> Value { ) -> Value {
match () { match () {
_ if val.is_big_int() => { _ if val.is_big_int() => {
@@ -336,21 +520,13 @@ fn to_value<'a>(
let list = (0..len) let list = (0..len)
.map(|i| { .map(|i| {
let val = val.get_index(scope, i).expect("infallible index operation"); let val = val.get_index(scope, i).expect("infallible index operation");
to_value( to_value(val, scope, symbols)
val,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
)
}) })
.collect(); .collect();
Value::List(List::new(list)) Value::List(List::new(list))
} }
_ if val.is_function() => { _ if val.is_function() => {
if let Some(primop) = to_primop(val, scope, primop_metadata_symbol) { if let Some(primop) = to_primop(val, scope, symbols.primop_metadata) {
primop primop
} else { } else {
Value::Func Value::Func
@@ -369,34 +545,26 @@ fn to_value<'a>(
let val = array let val = array
.get_index(scope, i * 2 + 1) .get_index(scope, i * 2 + 1)
.expect("infallible index operation"); .expect("infallible index operation");
let val = to_value( let val = to_value(val, scope, symbols);
val,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
);
(Symbol::new(Cow::Owned(key)), val) (Symbol::new(Cow::Owned(key)), val)
}) })
.collect(); .collect();
Value::AttrSet(AttrSet::new(attrs)) Value::AttrSet(AttrSet::new(attrs))
} }
_ if val.is_object() => { _ if val.is_object() => {
if is_thunk(val, scope, is_thunk_symbol) { if is_thunk(val, scope, symbols.is_thunk) {
return Value::Thunk; return Value::Thunk;
} }
if is_cycle(val, scope, is_cycle_symbol) { if is_cycle(val, scope, symbols.is_cycle) {
return Value::Repeated; return Value::Repeated;
} }
if let Some(path_val) = extract_path(val, scope, is_path_symbol) { if let Some(path_val) = extract_path(val, scope, symbols.is_path) {
return Value::Path(path_val); return Value::Path(path_val);
} }
if let Some(string_val) = extract_string_with_context(val, scope, has_context_symbol) { if let Some(string_val) = extract_string_with_context(val, scope, symbols.has_context) {
return Value::String(string_val); return Value::String(string_val);
} }
@@ -412,18 +580,7 @@ fn to_value<'a>(
.expect("infallible index operation"); .expect("infallible index operation");
let val = val.get(scope, key).expect("infallible operation"); let val = val.get(scope, key).expect("infallible operation");
let key = key.to_rust_string_lossy(scope); let key = key.to_rust_string_lossy(scope);
( (Symbol::from(key), to_value(val, scope, symbols))
Symbol::from(key),
to_value(
val,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
),
)
}) })
.collect(); .collect();
Value::AttrSet(AttrSet::new(attrs)) Value::AttrSet(AttrSet::new(attrs))

View File

@@ -2,6 +2,14 @@
// Alias for the future `!` type. // Alias for the future `!` type.
use core::convert::Infallible as Never; use core::convert::Infallible as Never;
use std::cell::RefCell;
use std::net::SocketAddr;
use std::pin::pin;
use std::process;
use std::rc::Rc;
use std::task::Poll;
use std::thread;
use deno_core::InspectorMsg; use deno_core::InspectorMsg;
use deno_core::InspectorSessionChannels; use deno_core::InspectorSessionChannels;
use deno_core::InspectorSessionKind; use deno_core::InspectorSessionKind;
@@ -24,13 +32,6 @@ use fastwebsockets::WebSocket;
use hashbrown::HashMap; use hashbrown::HashMap;
use hyper::body::Bytes; use hyper::body::Bytes;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use std::cell::RefCell;
use std::net::SocketAddr;
use std::pin::pin;
use std::process;
use std::rc::Rc;
use std::task::Poll;
use std::thread;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use uuid::Uuid; use uuid::Uuid;

View File

@@ -10,6 +10,7 @@ use regex::Regex;
use rust_embed::Embed; use rust_embed::Embed;
use super::{NixRuntimeError, OpStateExt, RuntimeContext}; use super::{NixRuntimeError, OpStateExt, RuntimeContext};
use crate::bytecode::{Bytecode, Constant};
use crate::error::Source; use crate::error::Source;
use crate::store::Store as _; use crate::store::Store as _;
@@ -60,7 +61,7 @@ where
#[derive(Embed)] #[derive(Embed)]
#[folder = "src/runtime/corepkgs"] #[folder = "src/runtime/corepkgs"]
pub(crate) struct CorePkgs; struct CorePkgs;
fn new_simple_jserror(msg: String) -> Box<JsError> { fn new_simple_jserror(msg: String) -> Box<JsError> {
JsError { JsError {
@@ -79,35 +80,67 @@ fn new_simple_jserror(msg: String) -> Box<JsError> {
.into() .into()
} }
struct Compiled(String); struct BytecodeRet {
impl<'a> ToV8<'a> for Compiled { bytecode: Bytecode,
new_strings: *const [String],
new_constants: *const [Constant],
strings_base: usize,
constants_base: usize,
}
impl<'a> ToV8<'a> for BytecodeRet {
type Error = Box<JsError>; type Error = Box<JsError>;
#[allow(clippy::unwrap_used)]
fn to_v8<'i>( fn to_v8<'i>(
self, self,
scope: &mut v8::PinScope<'a, 'i>, scope: &mut v8::PinScope<'a, 'i>,
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> { ) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
let Ok(script) = self.0.to_v8(scope); let global = scope.get_current_context().global(scope);
let Some(source) = script.to_string(scope) else { let nix_key = v8::String::new(scope, "Nix")
unsafe { std::hint::unreachable_unchecked() } .ok_or_else(|| new_simple_jserror("failed to create v8 string".into()))?;
}; let nix_obj = global
let tc = std::pin::pin!(v8::TryCatch::new(scope)); .get(scope, nix_key.into())
let mut scope = tc.init(); .ok_or_else(|| new_simple_jserror("failed to get Nix global".into()))?
let Some(compiled) = v8::Script::compile(&scope, source, None) else { .to_object(scope)
let msg = scope .ok_or_else(|| new_simple_jserror("Nix is not an object".into()))?;
.exception()
.map(|e| e.to_rust_string_lossy(&scope)) let s_key = v8::String::new(scope, "strings").unwrap();
.unwrap_or_else(|| "failed to compile code".into()); let s_array: v8::Local<v8::Array> = nix_obj
return Err(new_simple_jserror(msg)); .get(scope, s_key.into())
}; .unwrap()
match compiled.run(&scope) { .try_into()
Some(val) => Ok(val), .unwrap();
None => Err(scope for (i, s) in unsafe { &*self.new_strings }.iter().enumerate() {
.exception() let idx = (self.strings_base + i) as u32;
.map(|e| JsError::from_v8_exception(&mut scope, e)) let val = v8::String::new(scope, s).unwrap();
.unwrap_or_else(|| { s_array.set_index(scope, idx, val.into());
new_simple_jserror("script execution failed unexpectedly".into())
})),
} }
let k_key = v8::String::new(scope, "constants").unwrap();
let k_array: v8::Local<v8::Array> = nix_obj
.get(scope, k_key.into())
.unwrap()
.try_into()
.unwrap();
for (i, c) in unsafe { &*self.new_constants }.iter().enumerate() {
let idx = (self.constants_base + i) as u32;
let val: v8::Local<v8::Value> = match c {
Constant::Int(n) => v8::BigInt::new_from_i64(scope, *n).into(),
Constant::Float(bits) => v8::Number::new(scope, f64::from_bits(*bits)).into(),
};
k_array.set_index(scope, idx, val);
}
let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(self.bytecode.code);
let ab = v8::ArrayBuffer::with_backing_store(scope, &store.make_shared());
let u8a = v8::Uint8Array::new(scope, ab, 0, ab.byte_length())
.ok_or_else(|| new_simple_jserror("failed to create Uint8Array".into()))?;
let dir = v8::String::new(scope, &self.bytecode.current_dir)
.ok_or_else(|| new_simple_jserror("failed to create dir string".into()))?;
let arr = v8::Array::new_with_elements(scope, &[u8a.into(), dir.into()]);
Ok(arr.into())
} }
} }
@@ -115,7 +148,7 @@ impl<'a> ToV8<'a> for Compiled {
pub(super) fn op_import<Ctx: RuntimeContext>( pub(super) fn op_import<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] path: String, #[string] path: String,
) -> Result<Compiled> { ) -> Result<BytecodeRet> {
let _span = tracing::info_span!("op_import", path = %path).entered(); let _span = tracing::info_span!("op_import", path = %path).entered();
let ctx: &mut Ctx = state.get_ctx_mut(); let ctx: &mut Ctx = state.get_ctx_mut();
@@ -131,8 +164,17 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
.into(), .into(),
); );
ctx.add_source(source.clone()); ctx.add_source(source.clone());
let code = ctx.compile(source).map_err(|err| err.to_string())?; let bytecode = ctx
return Ok(Compiled(code)); .compile_bytecode(source)
.map_err(|err| err.to_string())?;
let (new_strings, new_constants, strings_base, constants_base) = ctx.get_unsynced();
return Ok(BytecodeRet {
bytecode,
new_strings,
new_constants,
strings_base,
constants_base,
});
} else { } else {
return Err(format!("Corepkg not found: {}", corepkg_name).into()); return Err(format!("Corepkg not found: {}", corepkg_name).into());
} }
@@ -156,17 +198,25 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
tracing::debug!("Compiling file"); tracing::debug!("Compiling file");
ctx.add_source(source.clone()); ctx.add_source(source.clone());
let code = ctx.compile(source).map_err(|err| err.to_string())?; let bytecode = ctx
Ok(Compiled(code)) .compile_bytecode(source)
.map_err(|err| err.to_string())?;
let (new_strings, new_constants, strings_base, constants_base) = ctx.get_unsynced();
Ok(BytecodeRet {
bytecode,
new_strings,
new_constants,
strings_base,
constants_base,
})
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string]
pub(super) fn op_scoped_import<Ctx: RuntimeContext>( pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] path: String, #[string] path: String,
#[scoped] scope: Vec<String>, #[serde] scope: Vec<String>,
) -> Result<String> { ) -> Result<BytecodeRet> {
let _span = tracing::info_span!("op_scoped_import", path = %path).entered(); let _span = tracing::info_span!("op_scoped_import", path = %path).entered();
let ctx: &mut Ctx = state.get_ctx_mut(); let ctx: &mut Ctx = state.get_ctx_mut();
@@ -185,18 +235,26 @@ pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
tracing::debug!("Compiling file for scoped import"); tracing::debug!("Compiling file for scoped import");
ctx.add_source(source.clone()); ctx.add_source(source.clone());
Ok(ctx let bytecode = ctx
.compile_scoped(source, scope) .compile_bytecode_scoped(source, scope)
.map_err(|err| err.to_string())?) .map_err(|err| err.to_string())?;
let (new_strings, new_constants, strings_base, constants_base) = ctx.get_unsynced();
Ok(BytecodeRet {
bytecode,
new_strings,
new_constants,
strings_base,
constants_base,
})
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_read_file(#[string] path: String) -> Result<String> { pub(super) fn op_read_file(#[string] path: String) -> Result<String> {
Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?) Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?)
} }
#[deno_core::op2(fast)] #[deno_core::op2(fast, reentrant)]
pub(super) fn op_path_exists(#[string] path: String) -> bool { pub(super) fn op_path_exists(#[string] path: String) -> bool {
let must_be_dir = path.ends_with('/') || path.ends_with("/."); let must_be_dir = path.ends_with('/') || path.ends_with("/.");
let p = Path::new(&path); let p = Path::new(&path);
@@ -211,7 +269,7 @@ pub(super) fn op_path_exists(#[string] path: String) -> bool {
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> { pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
let path = Path::new(&path); let path = Path::new(&path);
@@ -232,7 +290,7 @@ pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
Ok(type_str.to_string()) Ok(type_str.to_string())
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static str>> { pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static str>> {
let path = Path::new(&path); let path = Path::new(&path);
@@ -273,7 +331,7 @@ pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static
Ok(Map(result)) Ok(Map(result))
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_resolve_path( pub(super) fn op_resolve_path(
#[string] current_dir: String, #[string] current_dir: String,
@@ -312,7 +370,7 @@ pub(super) fn op_resolve_path(
Ok(normalized.to_string_lossy().to_string()) Ok(normalized.to_string_lossy().to_string())
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_make_placeholder(#[string] output: String) -> String { pub(super) fn op_make_placeholder(#[string] output: String) -> String {
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@@ -341,7 +399,7 @@ impl<'a> ToV8<'a> for StringOrU32 {
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_decode_span<Ctx: RuntimeContext>( pub(super) fn op_decode_span<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[smi] span_id: u32, #[smi] span_id: u32,
@@ -390,7 +448,7 @@ mod private {
} }
use private::*; use private::*;
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_parse_hash( pub(super) fn op_parse_hash(
#[string] hash_str: String, #[string] hash_str: String,
#[string] algo: Option<String>, #[string] algo: Option<String>,
@@ -416,7 +474,7 @@ pub(super) fn op_parse_hash(
}) })
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_add_path<Ctx: RuntimeContext>( pub(super) fn op_add_path<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
@@ -425,11 +483,12 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
recursive: bool, recursive: bool,
#[string] sha256: Option<String>, #[string] sha256: Option<String>,
) -> Result<String> { ) -> Result<String> {
use nix_compat::nixhash::{HashAlgo, NixHash};
use sha2::{Digest, Sha256};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use nix_compat::nixhash::{HashAlgo, NixHash};
use sha2::{Digest, Sha256};
let path_obj = Path::new(&path); let path_obj = Path::new(&path);
if !path_obj.exists() { if !path_obj.exists() {
@@ -495,7 +554,7 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
Ok(store_path) Ok(store_path)
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_store_path<Ctx: RuntimeContext>( pub(super) fn op_store_path<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
@@ -516,7 +575,7 @@ pub(super) fn op_store_path<Ctx: RuntimeContext>(
Ok(path) Ok(path)
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_to_file<Ctx: RuntimeContext>( pub(super) fn op_to_file<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
@@ -533,7 +592,7 @@ pub(super) fn op_to_file<Ctx: RuntimeContext>(
Ok(store_path) Ok(store_path)
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>( pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
@@ -565,7 +624,7 @@ pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
Ok(store_path) Ok(store_path)
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_get_env(#[string] key: String) -> Result<String> { pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
match std::env::var(key) { match std::env::var(key) {
@@ -575,7 +634,7 @@ pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> { pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> {
fn walk_recursive( fn walk_recursive(
base: &Path, base: &Path,
@@ -629,7 +688,7 @@ pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)
Ok(results) Ok(results)
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>( pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
@@ -639,9 +698,10 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
#[string] sha256: Option<String>, #[string] sha256: Option<String>,
#[scoped] include_paths: Vec<String>, #[scoped] include_paths: Vec<String>,
) -> Result<String> { ) -> Result<String> {
use std::fs;
use nix_compat::nixhash::{HashAlgo, NixHash}; use nix_compat::nixhash::{HashAlgo, NixHash};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::fs;
let src = Path::new(&src_path); let src = Path::new(&src_path);
if !src.exists() { if !src.exists() {
@@ -735,7 +795,7 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
Ok(store_path) Ok(store_path)
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_match( pub(super) fn op_match(
state: &mut OpState, state: &mut OpState,
#[string] regex: String, #[string] regex: String,
@@ -759,7 +819,7 @@ pub(super) fn op_match(
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_split( pub(super) fn op_split(
state: &mut OpState, state: &mut OpState,
#[string] regex: String, #[string] regex: String,
@@ -925,14 +985,14 @@ fn toml_to_nix(value: toml::Value) -> Result<NixJsonValue> {
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_from_json(#[string] json_str: String) -> Result<NixJsonValue> { pub(super) fn op_from_json(#[string] json_str: String) -> Result<NixJsonValue> {
let parsed: serde_json::Value = serde_json::from_str(&json_str) let parsed: serde_json::Value = serde_json::from_str(&json_str)
.map_err(|e| NixRuntimeError::from(format!("builtins.fromJSON: {e}")))?; .map_err(|e| NixRuntimeError::from(format!("builtins.fromJSON: {e}")))?;
Ok(json_to_nix(parsed)) Ok(json_to_nix(parsed))
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> { pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> {
let parsed: toml::Value = toml::from_str(&toml_str) let parsed: toml::Value = toml::from_str(&toml_str)
.map_err(|e| NixRuntimeError::from(format!("while parsing TOML: {e}")))?; .map_err(|e| NixRuntimeError::from(format!("while parsing TOML: {e}")))?;
@@ -966,7 +1026,7 @@ fn output_path_name(drv_name: &str, output: &str) -> String {
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>( pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] name: String, #[string] name: String,
@@ -1180,7 +1240,7 @@ fn op_make_fixed_output_path_impl(
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) -> Result<String> { pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) -> Result<String> {
use sha2::{Digest, Sha256, Sha512}; use sha2::{Digest, Sha256, Sha512};
@@ -1217,7 +1277,7 @@ pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) ->
Ok(hex::encode(hash_bytes)) Ok(hex::encode(hash_bytes))
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Result<String> { pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Result<String> {
let data = std::fs::read(&path) let data = std::fs::read(&path)
@@ -1257,7 +1317,7 @@ pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Re
Ok(hex::encode(hash_bytes)) Ok(hex::encode(hash_bytes))
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
#[string] #[string]
pub(super) fn op_convert_hash( pub(super) fn op_convert_hash(
#[string] hash: &str, #[string] hash: &str,
@@ -1830,7 +1890,7 @@ impl<'a> FromV8<'a> for ToXmlResult {
} }
} }
#[deno_core::op2] #[deno_core::op2(reentrant)]
pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) { pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) {
(value.xml, value.context) (value.xml, value.context)
} }

500
nix-js/src/runtime/value.rs Normal file
View File

@@ -0,0 +1,500 @@
use std::fmt;
use std::marker::PhantomData;
use boxing::nan::raw::{RawBox, RawStore, RawTag, Value as RawValue};
use gc_arena::{Collect, Gc};
use hashbrown::HashTable;
// ---------------------------------------------------------------------------
// Tag layout
// ---------------------------------------------------------------------------
//
// Positive tags (sign=false) — inline data in 6 bytes:
// 1: SmallInt — i32
// 2: Bool — u8 (0 or 1)
// 3: Null — no payload
// 4: SmallString — SmallStringId (u32)
//
// Negative tags (sign=true) — GC heap pointer (48-bit address):
// 1: BigInt → Gc<'gc, i64>
// 2: String → Gc<'gc, NixString>
// 3: SmallAttrSet → Gc<'gc, SmallAttrSet<'gc>>
// 4: AttrSet → Gc<'gc, AttrSet<'gc>>
// 5: List → Gc<'gc, Box<[Value<'gc>]>>
//
// Floats are stored directly as f64 (no tag).
const TAG_SMI: (bool, u8) = (false, 1);
const TAG_BOOL: (bool, u8) = (false, 2);
const TAG_NULL: (bool, u8) = (false, 3);
const TAG_SMALL_STRING: (bool, u8) = (false, 4);
const TAG_BIG_INT: (bool, u8) = (true, 1);
const TAG_STRING: (bool, u8) = (true, 2);
const TAG_SMALL_ATTRS: (bool, u8) = (true, 3);
const TAG_ATTRS: (bool, u8) = (true, 4);
const TAG_LIST: (bool, u8) = (true, 5);
/// # Nix runtime value representation
///
/// NaN-boxed value fitting in 8 bytes. Morally equivalent to:
/// ```ignore
/// enum NixValue<'gc> {
/// Float(SingleNaNF64),
/// SmallInt(i32),
/// BigInt(Gc<'gc, i64>),
/// Bool(bool),
/// Null,
/// SmallString(SmallStringId),
/// String(Gc<'gc, NixString>),
/// SmallAttrSet(Gc<'gc, SmallAttrSet<'gc>>),
/// AttrSet(Gc<'gc, AttrSet<'gc>>),
/// List(Gc<'gc, Box<[Value<'gc>]>>),
/// }
/// ```
#[repr(transparent)]
pub(crate) struct Value<'gc> {
raw: RawBox,
_marker: PhantomData<Gc<'gc, ()>>,
}
impl<'gc> Clone for Value<'gc> {
#[inline]
fn clone(&self) -> Self {
Self {
raw: self.raw.clone(),
_marker: PhantomData,
}
}
}
unsafe impl<'gc> Collect for Value<'gc> {
fn trace(&self, cc: &gc_arena::Collection) {
let Some(tag) = self.raw.tag() else { return };
let (neg, val) = tag.neg_val();
if !neg {
return; // inline values hold no GC pointers
}
// Negative tags are heap pointers — reconstruct the Gc and trace it.
unsafe {
match val {
1 => self.load_gc::<i64>().trace(cc),
2 => self.load_gc::<NixString>().trace(cc),
3 => self.load_gc::<SmallAttrSet<'gc>>().trace(cc),
4 => self.load_gc::<AttrSet<'gc>>().trace(cc),
5 => self.load_gc::<Box<[Value<'gc>]>>().trace(cc),
_ => debug_assert!(false, "invalid negative tag value: {val}"),
}
}
}
fn needs_trace() -> bool
where
Self: Sized,
{
true
}
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
impl<'gc> Value<'gc> {
#[inline(always)]
fn mk_tag(neg: bool, val: u8) -> RawTag {
debug_assert!((1..=7).contains(&val));
// Safety: val is asserted to be in 1..=7.
unsafe { RawTag::new_unchecked(neg, val) }
}
#[inline(always)]
fn from_raw_value(rv: RawValue) -> Self {
Self {
raw: RawBox::from_value(rv),
_marker: PhantomData,
}
}
/// Store a GC pointer with the given (negative) tag value.
#[inline(always)]
fn store_gc<T>(tag_val: u8, gc: Gc<'gc, T>) -> Self {
let ptr = Gc::as_ptr(gc);
Self::from_raw_value(RawValue::store(Self::mk_tag(true, tag_val), ptr))
}
/// Load a GC pointer from a value with a negative tag.
///
/// # Safety
///
/// The value must actually store a `Gc<'gc, T>` with the matching type.
#[inline(always)]
unsafe fn load_gc<T>(&self) -> Gc<'gc, T> {
unsafe {
let rv = self.raw.value().unwrap_unchecked();
let ptr: *const T = <*const T as RawStore>::from_val(rv);
Gc::from_ptr(ptr)
}
}
/// Returns the `(negative, val)` tag, or `None` for a float.
#[inline(always)]
fn tag_nv(&self) -> Option<(bool, u8)> {
self.raw.tag().map(|t| t.neg_val())
}
}
// ---------------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------------
impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn new_float(val: f64) -> Self {
Self {
raw: RawBox::from_float(val),
_marker: PhantomData,
}
}
#[inline]
pub(crate) fn new_smi(val: i32) -> Self {
Self::from_raw_value(RawValue::store(Self::mk_tag(TAG_SMI.0, TAG_SMI.1), val))
}
#[inline]
pub(crate) fn new_int(gc: Gc<'gc, i64>) -> Self {
Self::store_gc(TAG_BIG_INT.1, gc)
}
#[inline]
pub(crate) fn new_bool(val: bool) -> Self {
Self::from_raw_value(RawValue::store(
Self::mk_tag(TAG_BOOL.0, TAG_BOOL.1),
val,
))
}
#[inline]
pub(crate) fn new_null() -> Self {
Self::from_raw_value(RawValue::empty(Self::mk_tag(TAG_NULL.0, TAG_NULL.1)))
}
#[inline]
pub(crate) fn new_small_string(id: SmallStringId) -> Self {
Self::from_raw_value(RawValue::store(
Self::mk_tag(TAG_SMALL_STRING.0, TAG_SMALL_STRING.1),
id.0,
))
}
#[inline]
pub(crate) fn new_string(gc: Gc<'gc, NixString>) -> Self {
Self::store_gc(TAG_STRING.1, gc)
}
#[inline]
pub(crate) fn new_small_attrs(gc: Gc<'gc, SmallAttrSet<'gc>>) -> Self {
Self::store_gc(TAG_SMALL_ATTRS.1, gc)
}
#[inline]
pub(crate) fn new_attrs(gc: Gc<'gc, AttrSet<'gc>>) -> Self {
Self::store_gc(TAG_ATTRS.1, gc)
}
#[inline]
pub(crate) fn new_list(gc: Gc<'gc, Box<[Value<'gc>]>>) -> Self {
Self::store_gc(TAG_LIST.1, gc)
}
}
// ---------------------------------------------------------------------------
// Type checks
// ---------------------------------------------------------------------------
impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn is_float(&self) -> bool {
self.raw.is_float()
}
#[inline]
pub(crate) fn is_smi(&self) -> bool {
self.tag_nv() == Some(TAG_SMI)
}
#[inline]
pub(crate) fn is_big_int(&self) -> bool {
self.tag_nv() == Some(TAG_BIG_INT)
}
/// True for float, small int, or big int.
#[inline]
pub(crate) fn is_number(&self) -> bool {
match self.tag_nv() {
None => true,
Some(TAG_SMI) | Some(TAG_BIG_INT) => true,
_ => false,
}
}
#[inline]
pub(crate) fn is_bool(&self) -> bool {
self.tag_nv() == Some(TAG_BOOL)
}
#[inline]
pub(crate) fn is_null(&self) -> bool {
self.tag_nv() == Some(TAG_NULL)
}
#[inline]
pub(crate) fn is_small_string(&self) -> bool {
self.tag_nv() == Some(TAG_SMALL_STRING)
}
#[inline]
pub(crate) fn is_heap_string(&self) -> bool {
self.tag_nv() == Some(TAG_STRING)
}
/// True for small string or heap string.
#[inline]
pub(crate) fn is_string(&self) -> bool {
matches!(self.tag_nv(), Some(TAG_SMALL_STRING | TAG_STRING))
}
#[inline]
pub(crate) fn is_small_attrs(&self) -> bool {
self.tag_nv() == Some(TAG_SMALL_ATTRS)
}
#[inline]
pub(crate) fn is_heap_attrs(&self) -> bool {
self.tag_nv() == Some(TAG_ATTRS)
}
/// True for small or heap attr set.
#[inline]
pub(crate) fn is_attrs(&self) -> bool {
matches!(self.tag_nv(), Some(TAG_SMALL_ATTRS | TAG_ATTRS))
}
#[inline]
pub(crate) fn is_list(&self) -> bool {
self.tag_nv() == Some(TAG_LIST)
}
}
// ---------------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------------
impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn as_float(&self) -> Option<f64> {
self.raw.float().copied()
}
#[inline]
pub(crate) fn as_smi(&self) -> Option<i32> {
if self.is_smi() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
<i32 as RawStore>::from_val(rv)
})
} else {
None
}
}
#[inline]
pub(crate) fn as_big_int(&self) -> Option<Gc<'gc, i64>> {
if self.is_big_int() {
Some(unsafe { self.load_gc() })
} else {
None
}
}
/// Read the integer value as `i64` regardless of smi/big-int representation.
#[inline]
pub(crate) fn as_i64(&self) -> Option<i64> {
match self.tag_nv() {
Some(TAG_SMI) => Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
<i32 as RawStore>::from_val(rv) as i64
}),
Some(TAG_BIG_INT) => Some(unsafe { *self.load_gc::<i64>() }),
_ => None,
}
}
#[inline]
pub(crate) fn as_bool(&self) -> Option<bool> {
if self.is_bool() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
<bool as RawStore>::from_val(rv)
})
} else {
None
}
}
#[inline]
pub(crate) fn as_small_string(&self) -> Option<SmallStringId> {
if self.is_small_string() {
Some(SmallStringId(unsafe {
let rv = self.raw.value().unwrap_unchecked();
<u32 as RawStore>::from_val(rv)
}))
} else {
None
}
}
#[inline]
pub(crate) fn as_heap_string(&self) -> Option<Gc<'gc, NixString>> {
if self.is_heap_string() {
Some(unsafe { self.load_gc() })
} else {
None
}
}
#[inline]
pub(crate) fn as_small_attr_set(&self) -> Option<Gc<'gc, SmallAttrSet<'gc>>> {
if self.is_small_attrs() {
Some(unsafe { self.load_gc() })
} else {
None
}
}
#[inline]
pub(crate) fn as_heap_attr_set(&self) -> Option<Gc<'gc, AttrSet<'gc>>> {
if self.is_heap_attrs() {
Some(unsafe { self.load_gc() })
} else {
None
}
}
#[inline]
pub(crate) fn as_list(&self) -> Option<Gc<'gc, Box<[Value<'gc>]>>> {
if self.is_list() {
Some(unsafe { self.load_gc() })
} else {
None
}
}
}
// ---------------------------------------------------------------------------
// Debug
// ---------------------------------------------------------------------------
impl fmt::Debug for Value<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.tag_nv() {
None => {
let v = self.raw.float().unwrap();
write!(f, "Float({v:?})")
}
Some(TAG_SMI) => write!(f, "SmallInt({:?})", self.as_smi().unwrap()),
Some(TAG_BOOL) => write!(f, "Bool({:?})", self.as_bool().unwrap()),
Some(TAG_NULL) => write!(f, "Null"),
Some(TAG_SMALL_STRING) => {
write!(f, "SmallString({:?})", self.as_small_string().unwrap())
}
Some(TAG_BIG_INT) => write!(f, "BigInt(Gc<..>)"),
Some(TAG_STRING) => write!(f, "String(Gc<..>)"),
Some(TAG_SMALL_ATTRS) => write!(f, "SmallAttrSet(Gc<..>)"),
Some(TAG_ATTRS) => write!(f, "AttrSet(Gc<..>)"),
Some(TAG_LIST) => write!(f, "List(Gc<..>)"),
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
}
}
}
// ===========================================================================
// Supporting types
// ===========================================================================
// TODO: size?
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Collect)]
#[collect(require_static)]
pub(crate) struct SmallStringId(u32);
/// Heap-allocated Nix string.
///
/// Stored on the GC heap via `Gc<'gc, NixString>`. The string data itself
/// lives in a standard `Box<str>` owned by this struct; the GC only manages
/// the outer allocation.
#[derive(Collect)]
#[collect(require_static)]
pub(crate) struct NixString {
data: Box<str>,
// TODO: string context for derivation dependency tracking
}
impl NixString {
pub(crate) fn new(s: impl Into<Box<str>>) -> Self {
Self { data: s.into() }
}
pub(crate) fn as_str(&self) -> &str {
&self.data
}
}
impl fmt::Debug for NixString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.data, f)
}
}
/// Fixed-size attribute set (up to 8 entries).
#[derive(Collect)]
#[collect(no_drop)]
pub(crate) struct SmallAttrSet<'gc> {
// TODO: proper key storage, length tracking, and lookup
inner: [Value<'gc>; 8],
}
/// Hash-table-backed attribute set.
pub(crate) struct AttrSet<'gc> {
inner: HashTable<AttrSetEntry<'gc>>,
}
unsafe impl<'gc> Collect for AttrSet<'gc> {
fn trace(&self, cc: &gc_arena::Collection) {
for entry in self.inner.iter() {
Collect::trace(&entry.key, cc);
Collect::trace(&entry.value, cc);
}
}
fn needs_trace() -> bool
where
Self: Sized,
{
true
}
}
#[derive(Collect)]
#[collect(no_drop)]
struct AttrSetEntry<'gc> {
key: AttrKey<'gc>,
value: Value<'gc>,
}
#[derive(Collect)]
#[collect(no_drop)]
pub(crate) enum AttrKey<'gc> {
Small(SmallStringId),
Large(Gc<'gc, str>),
}

View File

@@ -87,11 +87,12 @@ impl Store for DaemonStore {
recursive: bool, recursive: bool,
references: Vec<String>, references: Vec<String>,
) -> Result<String> { ) -> Result<String> {
use std::fs;
use nix_compat::nix_daemon::types::AddToStoreNarRequest; use nix_compat::nix_daemon::types::AddToStoreNarRequest;
use nix_compat::nixhash::{CAHash, NixHash}; use nix_compat::nixhash::{CAHash, NixHash};
use nix_compat::store_path::{StorePath, build_ca_path}; use nix_compat::store_path::{StorePath, build_ca_path};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::fs;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
let temp_file = NamedTempFile::new() let temp_file = NamedTempFile::new()
@@ -237,11 +238,12 @@ impl Store for DaemonStore {
content: &str, content: &str,
references: Vec<String>, references: Vec<String>,
) -> Result<String> { ) -> Result<String> {
use std::fs;
use nix_compat::nix_daemon::types::AddToStoreNarRequest; use nix_compat::nix_daemon::types::AddToStoreNarRequest;
use nix_compat::nixhash::CAHash; use nix_compat::nixhash::CAHash;
use nix_compat::store_path::{StorePath, build_text_path}; use nix_compat::store_path::{StorePath, build_text_path};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::fs;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
let temp_file = NamedTempFile::new() let temp_file = NamedTempFile::new()

View File

@@ -81,7 +81,7 @@ pub fn validate_store_path(store_dir: &str, path: &str) -> Result<()> {
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test_log::test]
fn test_valid_store_paths() { fn test_valid_store_paths() {
let store_dir = "/nix/store"; let store_dir = "/nix/store";
let valid_paths = vec![ let valid_paths = vec![
@@ -100,7 +100,7 @@ mod tests {
} }
} }
#[test] #[test_log::test]
fn test_invalid_store_paths() { fn test_invalid_store_paths() {
let store_dir = "/nix/store"; let store_dir = "/nix/store";
let invalid_paths = vec![ let invalid_paths = vec![

View File

@@ -1,7 +1,6 @@
use core::fmt::{Debug, Display, Formatter, Result as FmtResult}; use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
use core::hash::Hash; use core::hash::Hash;
use core::ops::Deref; use core::ops::Deref;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ops::DerefMut; use std::ops::DerefMut;

View File

@@ -1,32 +1,33 @@
use crate::utils::{eval, eval_result};
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::{eval, eval_result};
#[test_log::test]
fn arithmetic() { fn arithmetic() {
assert_eq!(eval("1 + 1"), Value::Int(2)); assert_eq!(eval("1 + 1"), Value::Int(2));
} }
#[test] #[test_log::test]
fn simple_function_application() { fn simple_function_application() {
assert_eq!(eval("(x: x) 1"), Value::Int(1)); assert_eq!(eval("(x: x) 1"), Value::Int(1));
} }
#[test] #[test_log::test]
fn curried_function() { fn curried_function() {
assert_eq!(eval("(x: y: x - y) 2 1"), Value::Int(1)); assert_eq!(eval("(x: y: x - y) 2 1"), Value::Int(1));
} }
#[test] #[test_log::test]
fn rec_attrset() { fn rec_attrset() {
assert_eq!(eval("rec { b = a; a = 1; }.b"), Value::Int(1)); assert_eq!(eval("rec { b = a; a = 1; }.b"), Value::Int(1));
} }
#[test] #[test_log::test]
fn let_binding() { fn let_binding() {
assert_eq!(eval("let b = a; a = 1; in b"), Value::Int(1)); assert_eq!(eval("let b = a; a = 1; in b"), Value::Int(1));
} }
#[test] #[test_log::test]
fn fibonacci() { fn fibonacci() {
assert_eq!( assert_eq!(
eval( eval(
@@ -36,7 +37,7 @@ fn fibonacci() {
); );
} }
#[test] #[test_log::test]
fn fixed_point_combinator() { fn fixed_point_combinator() {
assert_eq!( assert_eq!(
eval("((f: let x = f x; in x)(self: { x = 1; y = self.x + 1; })).y"), eval("((f: let x = f x; in x)(self: { x = 1; y = self.x + 1; })).y"),
@@ -44,17 +45,17 @@ fn fixed_point_combinator() {
); );
} }
#[test] #[test_log::test]
fn conditional_true() { fn conditional_true() {
assert_eq!(eval("if true then 1 else 0"), Value::Int(1)); assert_eq!(eval("if true then 1 else 0"), Value::Int(1));
} }
#[test] #[test_log::test]
fn conditional_false() { fn conditional_false() {
assert_eq!(eval("if false then 1 else 0"), Value::Int(0)); assert_eq!(eval("if false then 1 else 0"), Value::Int(0));
} }
#[test] #[test_log::test]
fn nested_let() { fn nested_let() {
assert_eq!( assert_eq!(
eval("let x = 1; in let y = x + 1; z = y + 1; in z"), eval("let x = 1; in let y = x + 1; z = y + 1; in z"),
@@ -62,7 +63,7 @@ fn nested_let() {
); );
} }
#[test] #[test_log::test]
fn rec_inherit_fails() { fn rec_inherit_fails() {
assert!(eval_result("{ inherit x; }").is_err()); assert!(eval_result("{ inherit x; }").is_err());
} }

View File

@@ -1,30 +1,32 @@
use crate::utils::eval;
use nix_js::value::{AttrSet, List, Value};
use std::collections::BTreeMap; use std::collections::BTreeMap;
#[test] use nix_js::value::{AttrSet, List, Value};
use crate::utils::eval;
#[test_log::test]
fn builtins_accessible() { fn builtins_accessible() {
let result = eval("builtins"); let result = eval("builtins");
assert!(matches!(result, Value::AttrSet(_))); assert!(matches!(result, Value::AttrSet(_)));
} }
#[test] #[test_log::test]
fn builtins_self_reference() { fn builtins_self_reference() {
let result = eval("builtins.builtins"); let result = eval("builtins.builtins");
assert!(matches!(result, Value::AttrSet(_))); assert!(matches!(result, Value::AttrSet(_)));
} }
#[test] #[test_log::test]
fn builtins_add() { fn builtins_add() {
assert_eq!(eval("builtins.add 1 2"), Value::Int(3)); assert_eq!(eval("builtins.add 1 2"), Value::Int(3));
} }
#[test] #[test_log::test]
fn builtins_length() { fn builtins_length() {
assert_eq!(eval("builtins.length [1 2 3]"), Value::Int(3)); assert_eq!(eval("builtins.length [1 2 3]"), Value::Int(3));
} }
#[test] #[test_log::test]
fn builtins_map() { fn builtins_map() {
assert_eq!( assert_eq!(
eval("builtins.map (x: x * 2) [1 2 3]"), eval("builtins.map (x: x * 2) [1 2 3]"),
@@ -32,7 +34,7 @@ fn builtins_map() {
); );
} }
#[test] #[test_log::test]
fn builtins_filter() { fn builtins_filter() {
assert_eq!( assert_eq!(
eval("builtins.filter (x: x > 1) [1 2 3]"), eval("builtins.filter (x: x > 1) [1 2 3]"),
@@ -40,7 +42,7 @@ fn builtins_filter() {
); );
} }
#[test] #[test_log::test]
fn builtins_attrnames() { fn builtins_attrnames() {
let result = eval("builtins.attrNames { a = 1; b = 2; }"); let result = eval("builtins.attrNames { a = 1; b = 2; }");
assert!(matches!(result, Value::List(_))); assert!(matches!(result, Value::List(_)));
@@ -49,12 +51,12 @@ fn builtins_attrnames() {
} }
} }
#[test] #[test_log::test]
fn builtins_head() { fn builtins_head() {
assert_eq!(eval("builtins.head [1 2 3]"), Value::Int(1)); assert_eq!(eval("builtins.head [1 2 3]"), Value::Int(1));
} }
#[test] #[test_log::test]
fn builtins_tail() { fn builtins_tail() {
assert_eq!( assert_eq!(
eval("builtins.tail [1 2 3]"), eval("builtins.tail [1 2 3]"),
@@ -62,17 +64,17 @@ fn builtins_tail() {
); );
} }
#[test] #[test_log::test]
fn builtins_in_let() { fn builtins_in_let() {
assert_eq!(eval("let b = builtins; in b.add 5 3"), Value::Int(8)); assert_eq!(eval("let b = builtins; in b.add 5 3"), Value::Int(8));
} }
#[test] #[test_log::test]
fn builtins_in_with() { fn builtins_in_with() {
assert_eq!(eval("with builtins; add 10 20"), Value::Int(30)); assert_eq!(eval("with builtins; add 10 20"), Value::Int(30));
} }
#[test] #[test_log::test]
fn builtins_nested_calls() { fn builtins_nested_calls() {
assert_eq!( assert_eq!(
eval("builtins.add (builtins.mul 2 3) (builtins.sub 10 5)"), eval("builtins.add (builtins.mul 2 3) (builtins.sub 10 5)"),
@@ -80,32 +82,32 @@ fn builtins_nested_calls() {
); );
} }
#[test] #[test_log::test]
fn builtins_is_list() { fn builtins_is_list() {
assert_eq!(eval("builtins.isList [1 2 3]"), Value::Bool(true)); assert_eq!(eval("builtins.isList [1 2 3]"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn builtins_is_attrs() { fn builtins_is_attrs() {
assert_eq!(eval("builtins.isAttrs { a = 1; }"), Value::Bool(true)); assert_eq!(eval("builtins.isAttrs { a = 1; }"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn builtins_is_function() { fn builtins_is_function() {
assert_eq!(eval("builtins.isFunction (x: x)"), Value::Bool(true)); assert_eq!(eval("builtins.isFunction (x: x)"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn builtins_is_null() { fn builtins_is_null() {
assert_eq!(eval("builtins.isNull null"), Value::Bool(true)); assert_eq!(eval("builtins.isNull null"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn builtins_is_bool() { fn builtins_is_bool() {
assert_eq!(eval("builtins.isBool true"), Value::Bool(true)); assert_eq!(eval("builtins.isBool true"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn builtins_shadowing() { fn builtins_shadowing() {
assert_eq!( assert_eq!(
eval("let builtins = { add = x: y: x - y; }; in builtins.add 5 3"), eval("let builtins = { add = x: y: x - y; }; in builtins.add 5 3"),
@@ -113,13 +115,13 @@ fn builtins_shadowing() {
); );
} }
#[test] #[test_log::test]
fn builtins_lazy_evaluation() { fn builtins_lazy_evaluation() {
let result = eval("builtins.builtins.builtins.add 1 1"); let result = eval("builtins.builtins.builtins.add 1 1");
assert_eq!(result, Value::Int(2)); assert_eq!(result, Value::Int(2));
} }
#[test] #[test_log::test]
fn builtins_foldl() { fn builtins_foldl() {
assert_eq!( assert_eq!(
eval("builtins.foldl' (acc: x: acc + x) 0 [1 2 3 4 5]"), eval("builtins.foldl' (acc: x: acc + x) 0 [1 2 3 4 5]"),
@@ -127,13 +129,13 @@ fn builtins_foldl() {
); );
} }
#[test] #[test_log::test]
fn builtins_elem() { fn builtins_elem() {
assert_eq!(eval("builtins.elem 2 [1 2 3]"), Value::Bool(true)); assert_eq!(eval("builtins.elem 2 [1 2 3]"), Value::Bool(true));
assert_eq!(eval("builtins.elem 5 [1 2 3]"), Value::Bool(false)); assert_eq!(eval("builtins.elem 5 [1 2 3]"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn builtins_concat_lists() { fn builtins_concat_lists() {
assert_eq!( assert_eq!(
eval("builtins.concatLists [[1 2] [3 4] [5]]"), eval("builtins.concatLists [[1 2] [3 4] [5]]"),
@@ -147,7 +149,7 @@ fn builtins_concat_lists() {
); );
} }
#[test] #[test_log::test]
fn builtins_compare_versions_basic() { fn builtins_compare_versions_basic() {
assert_eq!( assert_eq!(
eval("builtins.compareVersions \"1.0\" \"2.3\""), eval("builtins.compareVersions \"1.0\" \"2.3\""),
@@ -171,7 +173,7 @@ fn builtins_compare_versions_basic() {
); );
} }
#[test] #[test_log::test]
fn builtins_compare_versions_components() { fn builtins_compare_versions_components() {
assert_eq!( assert_eq!(
eval("builtins.compareVersions \"2.3.1\" \"2.3\""), eval("builtins.compareVersions \"2.3.1\" \"2.3\""),
@@ -183,7 +185,7 @@ fn builtins_compare_versions_components() {
); );
} }
#[test] #[test_log::test]
fn builtins_compare_versions_numeric_vs_alpha() { fn builtins_compare_versions_numeric_vs_alpha() {
// Numeric component comes before alpha component // Numeric component comes before alpha component
assert_eq!( assert_eq!(
@@ -196,7 +198,7 @@ fn builtins_compare_versions_numeric_vs_alpha() {
); );
} }
#[test] #[test_log::test]
fn builtins_compare_versions_pre() { fn builtins_compare_versions_pre() {
// "pre" is special: comes before everything except another "pre" // "pre" is special: comes before everything except another "pre"
assert_eq!( assert_eq!(
@@ -217,7 +219,7 @@ fn builtins_compare_versions_pre() {
); );
} }
#[test] #[test_log::test]
fn builtins_compare_versions_alpha() { fn builtins_compare_versions_alpha() {
// Alphabetic comparison // Alphabetic comparison
assert_eq!( assert_eq!(
@@ -230,7 +232,7 @@ fn builtins_compare_versions_alpha() {
); );
} }
#[test] #[test_log::test]
fn builtins_compare_versions_symmetry() { fn builtins_compare_versions_symmetry() {
// Test symmetry: compareVersions(a, b) == -compareVersions(b, a) // Test symmetry: compareVersions(a, b) == -compareVersions(b, a)
assert_eq!( assert_eq!(
@@ -243,7 +245,7 @@ fn builtins_compare_versions_symmetry() {
); );
} }
#[test] #[test_log::test]
fn builtins_compare_versions_complex() { fn builtins_compare_versions_complex() {
// Complex version strings with multiple components // Complex version strings with multiple components
assert_eq!( assert_eq!(
@@ -260,7 +262,7 @@ fn builtins_compare_versions_complex() {
); );
} }
#[test] #[test_log::test]
fn builtins_generic_closure() { fn builtins_generic_closure() {
assert_eq!( assert_eq!(
eval( eval(
@@ -276,7 +278,7 @@ fn builtins_generic_closure() {
); );
} }
#[test] #[test_log::test]
fn builtins_function_args() { fn builtins_function_args() {
assert_eq!( assert_eq!(
eval("builtins.functionArgs (x: 1)"), eval("builtins.functionArgs (x: 1)"),
@@ -313,7 +315,7 @@ fn builtins_function_args() {
); );
} }
#[test] #[test_log::test]
fn builtins_parse_drv_name() { fn builtins_parse_drv_name() {
let result = eval(r#"builtins.parseDrvName "nix-js-0.1.0pre""#).unwrap_attr_set(); let result = eval(r#"builtins.parseDrvName "nix-js-0.1.0pre""#).unwrap_attr_set();
assert_eq!(result.get("name"), Some(&Value::String("nix-js".into()))); assert_eq!(result.get("name"), Some(&Value::String("nix-js".into())));

View File

@@ -1,7 +1,8 @@
use crate::utils::eval_result;
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::eval_result;
#[test_log::test]
fn to_file_simple() { fn to_file_simple() {
let result = let result =
eval_result(r#"builtins.toFile "hello.txt" "Hello, World!""#).expect("Failed to evaluate"); eval_result(r#"builtins.toFile "hello.txt" "Hello, World!""#).expect("Failed to evaluate");
@@ -18,7 +19,7 @@ fn to_file_simple() {
} }
} }
#[test] #[test_log::test]
fn to_file_with_references() { fn to_file_with_references() {
let result = eval_result( let result = eval_result(
r#" r#"
@@ -41,7 +42,7 @@ fn to_file_with_references() {
} }
} }
#[test] #[test_log::test]
fn to_file_invalid_name_with_slash() { fn to_file_invalid_name_with_slash() {
let result = eval_result(r#"builtins.toFile "foo/bar.txt" "content""#); let result = eval_result(r#"builtins.toFile "foo/bar.txt" "content""#);
@@ -54,7 +55,7 @@ fn to_file_invalid_name_with_slash() {
); );
} }
#[test] #[test_log::test]
fn to_file_invalid_name_dot() { fn to_file_invalid_name_dot() {
let result = eval_result(r#"builtins.toFile "." "content""#); let result = eval_result(r#"builtins.toFile "." "content""#);
@@ -62,7 +63,7 @@ fn to_file_invalid_name_dot() {
assert!(result.unwrap_err().to_string().contains("invalid name")); assert!(result.unwrap_err().to_string().contains("invalid name"));
} }
#[test] #[test_log::test]
fn to_file_invalid_name_dotdot() { fn to_file_invalid_name_dotdot() {
let result = eval_result(r#"builtins.toFile ".." "content""#); let result = eval_result(r#"builtins.toFile ".." "content""#);
@@ -70,7 +71,7 @@ fn to_file_invalid_name_dotdot() {
assert!(result.unwrap_err().to_string().contains("invalid name")); assert!(result.unwrap_err().to_string().contains("invalid name"));
} }
#[test] #[test_log::test]
fn store_path_validation_not_in_store() { fn store_path_validation_not_in_store() {
let result = eval_result(r#"builtins.storePath "/tmp/foo""#); let result = eval_result(r#"builtins.storePath "/tmp/foo""#);
@@ -83,7 +84,7 @@ fn store_path_validation_not_in_store() {
); );
} }
#[test] #[test_log::test]
fn store_path_validation_malformed_hash() { fn store_path_validation_malformed_hash() {
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#) let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
.expect("Failed to create dummy file"); .expect("Failed to create dummy file");
@@ -111,7 +112,7 @@ fn store_path_validation_malformed_hash() {
); );
} }
#[test] #[test_log::test]
fn store_path_validation_missing_name() { fn store_path_validation_missing_name() {
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#) let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
.expect("Failed to create dummy file"); .expect("Failed to create dummy file");
@@ -139,7 +140,7 @@ fn store_path_validation_missing_name() {
); );
} }
#[test] #[test_log::test]
fn to_file_curried_application() { fn to_file_curried_application() {
let result = eval_result( let result = eval_result(
r#" r#"
@@ -161,7 +162,7 @@ fn to_file_curried_application() {
} }
} }
#[test] #[test_log::test]
fn to_file_number_conversion() { fn to_file_number_conversion() {
let result = eval_result(r#"builtins.toFile "number.txt" (builtins.toString 42)"#) let result = eval_result(r#"builtins.toFile "number.txt" (builtins.toString 42)"#)
.expect("Failed to evaluate"); .expect("Failed to evaluate");
@@ -175,7 +176,7 @@ fn to_file_number_conversion() {
} }
} }
#[test] #[test_log::test]
fn to_file_list_conversion() { fn to_file_list_conversion() {
let result = eval_result( let result = eval_result(
r#"builtins.toFile "list.txt" (builtins.concatStringsSep "\n" ["line1" "line2" "line3"])"#, r#"builtins.toFile "list.txt" (builtins.concatStringsSep "\n" ["line1" "line2" "line3"])"#,

View File

@@ -1,7 +1,8 @@
use crate::utils::{eval_deep, eval_deep_result};
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::{eval_deep, eval_deep_result};
#[test_log::test]
fn add_operator_preserves_derivation_context() { fn add_operator_preserves_derivation_context() {
let result = eval_deep( let result = eval_deep(
r#" r#"
@@ -37,7 +38,7 @@ fn add_operator_preserves_derivation_context() {
assert_eq!(result, nix_result); assert_eq!(result, nix_result);
} }
#[test] #[test_log::test]
fn derivation_minimal() { fn derivation_minimal() {
let result = eval_deep( let result = eval_deep(
r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#, r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
@@ -75,7 +76,7 @@ fn derivation_minimal() {
} }
} }
#[test] #[test_log::test]
fn derivation_with_args() { fn derivation_with_args() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -97,7 +98,7 @@ fn derivation_with_args() {
} }
} }
#[test] #[test_log::test]
fn derivation_to_string() { fn derivation_to_string() {
let result = eval_deep( let result = eval_deep(
r#"toString (derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; })"#, r#"toString (derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; })"#,
@@ -109,7 +110,7 @@ fn derivation_to_string() {
} }
} }
#[test] #[test_log::test]
fn derivation_missing_name() { fn derivation_missing_name() {
let result = let result =
eval_deep_result(r#"derivation { builder = "/bin/sh"; system = "x86_64-linux"; }"#); eval_deep_result(r#"derivation { builder = "/bin/sh"; system = "x86_64-linux"; }"#);
@@ -119,7 +120,7 @@ fn derivation_missing_name() {
assert!(err_msg.contains("missing required attribute 'name'")); assert!(err_msg.contains("missing required attribute 'name'"));
} }
#[test] #[test_log::test]
fn derivation_invalid_name_with_drv_suffix() { fn derivation_invalid_name_with_drv_suffix() {
let result = eval_deep_result( let result = eval_deep_result(
r#"derivation { name = "foo.drv"; builder = "/bin/sh"; system = "x86_64-linux"; }"#, r#"derivation { name = "foo.drv"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
@@ -130,7 +131,7 @@ fn derivation_invalid_name_with_drv_suffix() {
assert!(err_msg.contains("cannot end with .drv")); assert!(err_msg.contains("cannot end with .drv"));
} }
#[test] #[test_log::test]
fn derivation_missing_builder() { fn derivation_missing_builder() {
let result = eval_deep_result(r#"derivation { name = "test"; system = "x86_64-linux"; }"#); let result = eval_deep_result(r#"derivation { name = "test"; system = "x86_64-linux"; }"#);
@@ -139,7 +140,7 @@ fn derivation_missing_builder() {
assert!(err_msg.contains("missing required attribute 'builder'")); assert!(err_msg.contains("missing required attribute 'builder'"));
} }
#[test] #[test_log::test]
fn derivation_missing_system() { fn derivation_missing_system() {
let result = eval_deep_result(r#"derivation { name = "test"; builder = "/bin/sh"; }"#); let result = eval_deep_result(r#"derivation { name = "test"; builder = "/bin/sh"; }"#);
@@ -148,7 +149,7 @@ fn derivation_missing_system() {
assert!(err_msg.contains("missing required attribute 'system'")); assert!(err_msg.contains("missing required attribute 'system'"));
} }
#[test] #[test_log::test]
fn derivation_with_env_vars() { fn derivation_with_env_vars() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -169,7 +170,7 @@ fn derivation_with_env_vars() {
} }
} }
#[test] #[test_log::test]
fn derivation_strict() { fn derivation_strict() {
let result = eval_deep( let result = eval_deep(
r#"builtins.derivationStrict { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; }"#, r#"builtins.derivationStrict { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
@@ -186,7 +187,7 @@ fn derivation_strict() {
} }
} }
#[test] #[test_log::test]
fn derivation_deterministic_paths() { fn derivation_deterministic_paths() {
let expr = r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#; let expr = r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#;
@@ -202,7 +203,7 @@ fn derivation_deterministic_paths() {
} }
} }
#[test] #[test_log::test]
fn derivation_escaping_in_aterm() { fn derivation_escaping_in_aterm() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -222,7 +223,7 @@ fn derivation_escaping_in_aterm() {
} }
} }
#[test] #[test_log::test]
fn multi_output_two_outputs() { fn multi_output_two_outputs() {
let drv = eval_deep( let drv = eval_deep(
r#"derivation { r#"derivation {
@@ -265,7 +266,7 @@ fn multi_output_two_outputs() {
} }
} }
#[test] #[test_log::test]
fn multi_output_three_outputs() { fn multi_output_three_outputs() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -313,7 +314,7 @@ fn multi_output_three_outputs() {
} }
} }
#[test] #[test_log::test]
fn multi_output_backward_compat() { fn multi_output_backward_compat() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -339,7 +340,7 @@ fn multi_output_backward_compat() {
} }
} }
#[test] #[test_log::test]
fn multi_output_deterministic() { fn multi_output_deterministic() {
let result1 = eval_deep( let result1 = eval_deep(
r#"derivation { r#"derivation {
@@ -362,7 +363,7 @@ fn multi_output_deterministic() {
assert_eq!(result1, result2); assert_eq!(result1, result2);
} }
#[test] #[test_log::test]
fn fixed_output_sha256_flat() { fn fixed_output_sha256_flat() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -399,7 +400,7 @@ fn fixed_output_sha256_flat() {
} }
} }
#[test] #[test_log::test]
fn fixed_output_missing_hashalgo() { fn fixed_output_missing_hashalgo() {
assert!( assert!(
eval_deep_result( eval_deep_result(
@@ -414,7 +415,7 @@ fn fixed_output_missing_hashalgo() {
); );
} }
#[test] #[test_log::test]
fn fixed_output_recursive_mode() { fn fixed_output_recursive_mode() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -444,7 +445,7 @@ fn fixed_output_recursive_mode() {
} }
} }
#[test] #[test_log::test]
fn fixed_output_rejects_multi_output() { fn fixed_output_rejects_multi_output() {
let result = eval_deep_result( let result = eval_deep_result(
r#"derivation { r#"derivation {
@@ -462,7 +463,7 @@ fn fixed_output_rejects_multi_output() {
assert!(err_msg.contains("fixed-output") && err_msg.contains("one")); assert!(err_msg.contains("fixed-output") && err_msg.contains("one"));
} }
#[test] #[test_log::test]
fn fixed_output_invalid_hash_mode() { fn fixed_output_invalid_hash_mode() {
let result = eval_deep_result( let result = eval_deep_result(
r#"derivation { r#"derivation {
@@ -479,7 +480,7 @@ fn fixed_output_invalid_hash_mode() {
assert!(err_msg.contains("outputHashMode") && err_msg.contains("invalid")); assert!(err_msg.contains("outputHashMode") && err_msg.contains("invalid"));
} }
#[test] #[test_log::test]
fn structured_attrs_basic() { fn structured_attrs_basic() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -504,7 +505,7 @@ fn structured_attrs_basic() {
} }
} }
#[test] #[test_log::test]
fn structured_attrs_nested() { fn structured_attrs_nested() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -525,7 +526,7 @@ fn structured_attrs_nested() {
} }
} }
#[test] #[test_log::test]
fn structured_attrs_rejects_functions() { fn structured_attrs_rejects_functions() {
let result = eval_deep_result( let result = eval_deep_result(
r#"derivation { r#"derivation {
@@ -542,7 +543,7 @@ fn structured_attrs_rejects_functions() {
assert!(err_msg.contains("cannot convert lambda to JSON")); assert!(err_msg.contains("cannot convert lambda to JSON"));
} }
#[test] #[test_log::test]
fn structured_attrs_false() { fn structured_attrs_false() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -565,7 +566,7 @@ fn structured_attrs_false() {
} }
} }
#[test] #[test_log::test]
fn ignore_nulls_true() { fn ignore_nulls_true() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -587,7 +588,7 @@ fn ignore_nulls_true() {
} }
} }
#[test] #[test_log::test]
fn ignore_nulls_false() { fn ignore_nulls_false() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -610,7 +611,7 @@ fn ignore_nulls_false() {
} }
} }
#[test] #[test_log::test]
fn ignore_nulls_with_structured_attrs() { fn ignore_nulls_with_structured_attrs() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -634,7 +635,7 @@ fn ignore_nulls_with_structured_attrs() {
} }
} }
#[test] #[test_log::test]
fn all_features_combined() { fn all_features_combined() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {
@@ -661,7 +662,7 @@ fn all_features_combined() {
} }
} }
#[test] #[test_log::test]
fn fixed_output_with_structured_attrs() { fn fixed_output_with_structured_attrs() {
let result = eval_deep( let result = eval_deep(
r#"derivation { r#"derivation {

View File

@@ -1,6 +1,6 @@
use crate::utils::eval; use crate::utils::eval;
#[test] #[test_log::test]
fn test_find_file_corepkg_fetchurl() { fn test_find_file_corepkg_fetchurl() {
let result = eval( let result = eval(
r#" r#"
@@ -15,13 +15,13 @@ fn test_find_file_corepkg_fetchurl() {
assert!(result.to_string().contains("fetchurl.nix")); assert!(result.to_string().contains("fetchurl.nix"));
} }
#[test] #[test_log::test]
fn test_lookup_path_syntax() { fn test_lookup_path_syntax() {
let result = eval(r#"<nix/fetchurl.nix>"#); let result = eval(r#"<nix/fetchurl.nix>"#);
assert!(result.to_string().contains("fetchurl.nix")); assert!(result.to_string().contains("fetchurl.nix"));
} }
#[test] #[test_log::test]
fn test_import_corepkg() { fn test_import_corepkg() {
let result = eval( let result = eval(
r#" r#"

View File

@@ -1,22 +1,23 @@
use crate::utils::{eval, eval_result};
use nix_js::value::{List, Value}; use nix_js::value::{List, Value};
#[test] use crate::utils::{eval, eval_result};
#[test_log::test]
fn true_literal() { fn true_literal() {
assert_eq!(eval("true"), Value::Bool(true)); assert_eq!(eval("true"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn false_literal() { fn false_literal() {
assert_eq!(eval("false"), Value::Bool(false)); assert_eq!(eval("false"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn null_literal() { fn null_literal() {
assert_eq!(eval("null"), Value::Null); assert_eq!(eval("null"), Value::Null);
} }
#[test] #[test_log::test]
fn map_function() { fn map_function() {
assert_eq!( assert_eq!(
eval("map (x: x * 2) [1 2 3]"), eval("map (x: x * 2) [1 2 3]"),
@@ -24,23 +25,23 @@ fn map_function() {
); );
} }
#[test] #[test_log::test]
fn is_null_function() { fn is_null_function() {
assert_eq!(eval("isNull null"), Value::Bool(true)); assert_eq!(eval("isNull null"), Value::Bool(true));
assert_eq!(eval("isNull 5"), Value::Bool(false)); assert_eq!(eval("isNull 5"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn shadow_true() { fn shadow_true() {
assert_eq!(eval("let true = false; in true"), Value::Bool(false)); assert_eq!(eval("let true = false; in true"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn shadow_map() { fn shadow_map() {
assert_eq!(eval("let map = x: y: x; in map 1 2"), Value::Int(1)); assert_eq!(eval("let map = x: y: x; in map 1 2"), Value::Int(1));
} }
#[test] #[test_log::test]
fn mixed_usage() { fn mixed_usage() {
assert_eq!( assert_eq!(
eval("if true then map (x: x + 1) [1 2] else []"), eval("if true then map (x: x + 1) [1 2] else []"),
@@ -48,7 +49,7 @@ fn mixed_usage() {
); );
} }
#[test] #[test_log::test]
fn in_let_bindings() { fn in_let_bindings() {
assert_eq!( assert_eq!(
eval("let x = true; y = false; in x && y"), eval("let x = true; y = false; in x && y"),
@@ -56,18 +57,18 @@ fn in_let_bindings() {
); );
} }
#[test] #[test_log::test]
fn shadow_in_function() { fn shadow_in_function() {
assert_eq!(eval("(true: true) false"), Value::Bool(false)); assert_eq!(eval("(true: true) false"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn throw_function() { fn throw_function() {
let result = eval_result("throw \"error message\""); let result = eval_result("throw \"error message\"");
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn to_string_function() { fn to_string_function() {
assert_eq!(eval("toString 42"), Value::String("42".to_string())); assert_eq!(eval("toString 42"), Value::String("42".to_string()));
} }

View File

@@ -1,18 +1,19 @@
use crate::utils::{eval, eval_result};
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::{eval, eval_result};
#[test_log::test]
fn required_parameters() { fn required_parameters() {
assert_eq!(eval("({ a, b }: a + b) { a = 1; b = 2; }"), Value::Int(3)); assert_eq!(eval("({ a, b }: a + b) { a = 1; b = 2; }"), Value::Int(3));
} }
#[test] #[test_log::test]
fn missing_required_parameter() { fn missing_required_parameter() {
let result = eval_result("({ a, b }: a + b) { a = 1; }"); let result = eval_result("({ a, b }: a + b) { a = 1; }");
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn all_required_parameters_present() { fn all_required_parameters_present() {
assert_eq!( assert_eq!(
eval("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }"), eval("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }"),
@@ -20,13 +21,13 @@ fn all_required_parameters_present() {
); );
} }
#[test] #[test_log::test]
fn reject_unexpected_arguments() { fn reject_unexpected_arguments() {
let result = eval_result("({ a, b }: a + b) { a = 1; b = 2; c = 3; }"); let result = eval_result("({ a, b }: a + b) { a = 1; b = 2; c = 3; }");
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn ellipsis_accepts_extra_arguments() { fn ellipsis_accepts_extra_arguments() {
assert_eq!( assert_eq!(
eval("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }"), eval("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }"),
@@ -34,12 +35,12 @@ fn ellipsis_accepts_extra_arguments() {
); );
} }
#[test] #[test_log::test]
fn default_parameters() { fn default_parameters() {
assert_eq!(eval("({ a, b ? 5 }: a + b) { a = 1; }"), Value::Int(6)); assert_eq!(eval("({ a, b ? 5 }: a + b) { a = 1; }"), Value::Int(6));
} }
#[test] #[test_log::test]
fn override_default_parameter() { fn override_default_parameter() {
assert_eq!( assert_eq!(
eval("({ a, b ? 5 }: a + b) { a = 1; b = 10; }"), eval("({ a, b ? 5 }: a + b) { a = 1; b = 10; }"),
@@ -47,7 +48,7 @@ fn override_default_parameter() {
); );
} }
#[test] #[test_log::test]
fn at_pattern_alias() { fn at_pattern_alias() {
assert_eq!( assert_eq!(
eval("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }"), eval("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }"),
@@ -55,17 +56,17 @@ fn at_pattern_alias() {
); );
} }
#[test] #[test_log::test]
fn simple_parameter_no_validation() { fn simple_parameter_no_validation() {
assert_eq!(eval("(x: x.a + x.b) { a = 1; b = 2; }"), Value::Int(3)); assert_eq!(eval("(x: x.a + x.b) { a = 1; b = 2; }"), Value::Int(3));
} }
#[test] #[test_log::test]
fn simple_parameter_accepts_any_argument() { fn simple_parameter_accepts_any_argument() {
assert_eq!(eval("(x: x) 42"), Value::Int(42)); assert_eq!(eval("(x: x) 42"), Value::Int(42));
} }
#[test] #[test_log::test]
fn nested_function_parameters() { fn nested_function_parameters() {
assert_eq!( assert_eq!(
eval("({ a }: { b }: a + b) { a = 5; } { b = 3; }"), eval("({ a }: { b }: a + b) { a = 5; } { b = 3; }"),
@@ -73,12 +74,12 @@ fn nested_function_parameters() {
); );
} }
#[test] #[test_log::test]
fn pattern_param_simple_reference_in_default() { fn pattern_param_simple_reference_in_default() {
assert_eq!(eval("({ a, b ? a }: b) { a = 10; }"), Value::Int(10)); assert_eq!(eval("({ a, b ? a }: b) { a = 10; }"), Value::Int(10));
} }
#[test] #[test_log::test]
fn pattern_param_multiple_references_in_default() { fn pattern_param_multiple_references_in_default() {
assert_eq!( assert_eq!(
eval("({ a, b ? a + 5, c ? 1 }: b + c) { a = 10; }"), eval("({ a, b ? a + 5, c ? 1 }: b + c) { a = 10; }"),
@@ -86,7 +87,7 @@ fn pattern_param_multiple_references_in_default() {
); );
} }
#[test] #[test_log::test]
fn pattern_param_mutual_reference() { fn pattern_param_mutual_reference() {
assert_eq!( assert_eq!(
eval("({ a, b ? c + 1, c ? 5 }: b) { a = 1; }"), eval("({ a, b ? c + 1, c ? 5 }: b) { a = 1; }"),
@@ -94,7 +95,7 @@ fn pattern_param_mutual_reference() {
); );
} }
#[test] #[test_log::test]
fn pattern_param_override_mutual_reference() { fn pattern_param_override_mutual_reference() {
assert_eq!( assert_eq!(
eval("({ a, b ? c + 1, c ? 5 }: b) { a = 1; c = 10; }"), eval("({ a, b ? c + 1, c ? 5 }: b) { a = 1; c = 10; }"),
@@ -102,7 +103,7 @@ fn pattern_param_override_mutual_reference() {
); );
} }
#[test] #[test_log::test]
fn pattern_param_reference_list() { fn pattern_param_reference_list() {
assert_eq!( assert_eq!(
eval("({ a, b ? [ a 2 ] }: builtins.elemAt b 0) { a = 42; }"), eval("({ a, b ? [ a 2 ] }: builtins.elemAt b 0) { a = 42; }"),
@@ -110,7 +111,7 @@ fn pattern_param_reference_list() {
); );
} }
#[test] #[test_log::test]
fn pattern_param_alias_in_default() { fn pattern_param_alias_in_default() {
assert_eq!( assert_eq!(
eval("(args@{ a, b ? args.a + 10 }: b) { a = 5; }"), eval("(args@{ a, b ? args.a + 10 }: b) { a = 5; }"),

View File

@@ -1,9 +1,10 @@
use crate::utils::{eval, eval_result};
use nix_js::context::Context; use nix_js::context::Context;
use nix_js::error::Source; use nix_js::error::Source;
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::{eval, eval_result};
#[test_log::test]
fn import_absolute_path() { fn import_absolute_path() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let lib_path = temp_dir.path().join("nix_test_lib.nix"); let lib_path = temp_dir.path().join("nix_test_lib.nix");
@@ -14,7 +15,7 @@ fn import_absolute_path() {
assert_eq!(eval(&expr), Value::Int(8)); assert_eq!(eval(&expr), Value::Int(8));
} }
#[test] #[test_log::test]
fn import_nested() { fn import_nested() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
@@ -32,7 +33,7 @@ fn import_nested() {
assert_eq!(eval(&expr), Value::Int(30)); assert_eq!(eval(&expr), Value::Int(30));
} }
#[test] #[test_log::test]
fn import_relative_path() { fn import_relative_path() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let subdir = temp_dir.path().join("subdir"); let subdir = temp_dir.path().join("subdir");
@@ -63,7 +64,7 @@ fn import_relative_path() {
assert_eq!(eval(&expr), Value::Int(7)); assert_eq!(eval(&expr), Value::Int(7));
} }
#[test] #[test_log::test]
fn import_returns_function() { fn import_returns_function() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let func_path = temp_dir.path().join("nix_test_func.nix"); let func_path = temp_dir.path().join("nix_test_func.nix");
@@ -73,7 +74,7 @@ fn import_returns_function() {
assert_eq!(eval(&expr), Value::Int(10)); assert_eq!(eval(&expr), Value::Int(10));
} }
#[test] #[test_log::test]
fn import_with_complex_dependency_graph() { fn import_with_complex_dependency_graph() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
@@ -94,7 +95,7 @@ fn import_with_complex_dependency_graph() {
// Tests for builtins.path // Tests for builtins.path
#[test] #[test_log::test]
fn path_with_file() { fn path_with_file() {
let mut ctx = Context::new().unwrap(); let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
@@ -113,7 +114,7 @@ fn path_with_file() {
} }
} }
#[test] #[test_log::test]
fn path_with_custom_name() { fn path_with_custom_name() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("original.txt"); let test_file = temp_dir.path().join("original.txt");
@@ -133,7 +134,7 @@ fn path_with_custom_name() {
} }
} }
#[test] #[test_log::test]
fn path_with_directory_recursive() { fn path_with_directory_recursive() {
let mut ctx = Context::new().unwrap(); let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
@@ -156,7 +157,7 @@ fn path_with_directory_recursive() {
} }
} }
#[test] #[test_log::test]
fn path_flat_with_file() { fn path_flat_with_file() {
let mut ctx = Context::new().unwrap(); let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
@@ -176,7 +177,7 @@ fn path_flat_with_file() {
} }
} }
#[test] #[test_log::test]
fn path_flat_with_directory_fails() { fn path_flat_with_directory_fails() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("mydir"); let test_dir = temp_dir.path().join("mydir");
@@ -193,7 +194,7 @@ fn path_flat_with_directory_fails() {
assert!(err_msg.contains("recursive") || err_msg.contains("regular file")); assert!(err_msg.contains("recursive") || err_msg.contains("regular file"));
} }
#[test] #[test_log::test]
fn path_nonexistent_fails() { fn path_nonexistent_fails() {
let expr = r#"builtins.path { path = "/nonexistent/path/that/should/not/exist"; }"#; let expr = r#"builtins.path { path = "/nonexistent/path/that/should/not/exist"; }"#;
let result = eval_result(expr); let result = eval_result(expr);
@@ -203,7 +204,7 @@ fn path_nonexistent_fails() {
assert!(err_msg.contains("does not exist")); assert!(err_msg.contains("does not exist"));
} }
#[test] #[test_log::test]
fn path_missing_path_param() { fn path_missing_path_param() {
let expr = r#"builtins.path { name = "test"; }"#; let expr = r#"builtins.path { name = "test"; }"#;
let result = eval_result(expr); let result = eval_result(expr);
@@ -213,7 +214,7 @@ fn path_missing_path_param() {
assert!(err_msg.contains("path") && err_msg.contains("required")); assert!(err_msg.contains("path") && err_msg.contains("required"));
} }
#[test] #[test_log::test]
fn path_with_sha256() { fn path_with_sha256() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("hash_test.txt"); let test_file = temp_dir.path().join("hash_test.txt");
@@ -240,7 +241,7 @@ fn path_with_sha256() {
assert_eq!(store_path1, store_path2); assert_eq!(store_path1, store_path2);
} }
#[test] #[test_log::test]
fn path_deterministic() { fn path_deterministic() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("deterministic.txt"); let test_file = temp_dir.path().join("deterministic.txt");
@@ -258,7 +259,7 @@ fn path_deterministic() {
assert_eq!(result1, result2); assert_eq!(result1, result2);
} }
#[test] #[test_log::test]
fn read_file_type_regular_file() { fn read_file_type_regular_file() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt"); let test_file = temp_dir.path().join("test.txt");
@@ -268,7 +269,7 @@ fn read_file_type_regular_file() {
assert_eq!(eval(&expr), Value::String("regular".to_string())); assert_eq!(eval(&expr), Value::String("regular".to_string()));
} }
#[test] #[test_log::test]
fn read_file_type_directory() { fn read_file_type_directory() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("testdir"); let test_dir = temp_dir.path().join("testdir");
@@ -278,7 +279,7 @@ fn read_file_type_directory() {
assert_eq!(eval(&expr), Value::String("directory".to_string())); assert_eq!(eval(&expr), Value::String("directory".to_string()));
} }
#[test] #[test_log::test]
fn read_file_type_symlink() { fn read_file_type_symlink() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let target = temp_dir.path().join("target.txt"); let target = temp_dir.path().join("target.txt");
@@ -296,7 +297,7 @@ fn read_file_type_symlink() {
} }
} }
#[test] #[test_log::test]
fn read_dir_basic() { fn read_dir_basic() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("readdir_test"); let test_dir = temp_dir.path().join("readdir_test");
@@ -328,7 +329,7 @@ fn read_dir_basic() {
} }
} }
#[test] #[test_log::test]
fn read_dir_empty() { fn read_dir_empty() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("empty_dir"); let test_dir = temp_dir.path().join("empty_dir");
@@ -344,7 +345,7 @@ fn read_dir_empty() {
} }
} }
#[test] #[test_log::test]
fn read_dir_nonexistent_fails() { fn read_dir_nonexistent_fails() {
let expr = r#"builtins.readDir "/nonexistent/directory""#; let expr = r#"builtins.readDir "/nonexistent/directory""#;
let result = eval_result(expr); let result = eval_result(expr);
@@ -352,7 +353,7 @@ fn read_dir_nonexistent_fails() {
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn read_dir_on_file_fails() { fn read_dir_on_file_fails() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt"); let test_file = temp_dir.path().join("test.txt");

View File

@@ -42,7 +42,7 @@ fn format_value(value: &Value) -> String {
macro_rules! eval_okay_test { macro_rules! eval_okay_test {
($(#[$attr:meta])* $name:ident$(, $pre:expr)?) => { ($(#[$attr:meta])* $name:ident$(, $pre:expr)?) => {
$(#[$attr])* $(#[$attr])*
#[test] #[test_log::test]
fn $name() { fn $name() {
$(($pre)();)? $(($pre)();)?
let test_name = concat!("eval-okay-", stringify!($name)) let test_name = concat!("eval-okay-", stringify!($name))
@@ -74,7 +74,7 @@ macro_rules! eval_okay_test {
macro_rules! eval_fail_test { macro_rules! eval_fail_test {
($name:ident) => { ($name:ident) => {
#[test] #[test_log::test]
fn $name() { fn $name() {
let test_name = concat!("eval-fail-", stringify!($name)) let test_name = concat!("eval-fail-", stringify!($name))
.replace("_", "-") .replace("_", "-")
@@ -192,14 +192,11 @@ eval_okay_test!(
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!( eval_okay_test!(path_string_interpolation, || {
path_string_interpolation, unsafe {
|| { std::env::set_var("HOME", "/fake-home");
unsafe {
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);

View File

@@ -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 ]

View File

@@ -1 +1 @@
[ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ [ «repeated» ] ] ] [ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ «repeated» ] ]

View File

@@ -6,6 +6,8 @@ trace
toString toString
(deepSeq "x") (deepSeq "x")
(a: a) (a: a)
# [ «repeated» ] instead of [ [ «repeated» ] ]
# matches Lix's behaviour
( (
let let
x = [ x ]; x = [ x ];

View File

@@ -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» ] ]

View File

@@ -6,6 +6,7 @@ mod findfile;
mod free_globals; mod free_globals;
mod functions; mod functions;
mod io_operations; mod io_operations;
mod lang;
mod numeric_types; mod numeric_types;
mod operators; mod operators;
mod path_operations; mod path_operations;
@@ -13,5 +14,4 @@ mod regex;
mod string_context; mod string_context;
mod thunk_scope; mod thunk_scope;
mod to_string; mod to_string;
mod lang;
mod utils; mod utils;

View File

@@ -1,12 +1,13 @@
use crate::utils::eval;
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::eval;
#[test_log::test]
fn large_i64_max() { fn large_i64_max() {
assert_eq!(eval("9223372036854775807"), Value::Int(9223372036854775807)); assert_eq!(eval("9223372036854775807"), Value::Int(9223372036854775807));
} }
#[test] #[test_log::test]
fn large_i64_negative() { fn large_i64_negative() {
assert_eq!( assert_eq!(
eval("-9223372036854775807"), eval("-9223372036854775807"),
@@ -14,7 +15,7 @@ fn large_i64_negative() {
); );
} }
#[test] #[test_log::test]
fn large_number_arithmetic() { fn large_number_arithmetic() {
assert_eq!( assert_eq!(
eval("5000000000000000000 + 3000000000000000000"), eval("5000000000000000000 + 3000000000000000000"),
@@ -22,33 +23,33 @@ fn large_number_arithmetic() {
); );
} }
#[test] #[test_log::test]
fn is_int_with_int() { fn is_int_with_int() {
assert_eq!(eval("builtins.isInt 42"), Value::Bool(true)); assert_eq!(eval("builtins.isInt 42"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn is_int_with_float() { fn is_int_with_float() {
assert_eq!(eval("builtins.isInt 42.0"), Value::Bool(false)); assert_eq!(eval("builtins.isInt 42.0"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn is_float_with_int() { fn is_float_with_int() {
assert_eq!(eval("builtins.isFloat 42"), Value::Bool(false)); assert_eq!(eval("builtins.isFloat 42"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn is_float_with_float() { fn is_float_with_float() {
assert_eq!(eval("builtins.isFloat 42.5"), Value::Bool(true)); assert_eq!(eval("builtins.isFloat 42.5"), Value::Bool(true));
assert_eq!(eval("builtins.isFloat 1.0"), Value::Bool(true)); assert_eq!(eval("builtins.isFloat 1.0"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn typeof_int() { fn typeof_int() {
assert_eq!(eval("builtins.typeOf 1"), Value::String("int".to_string())); assert_eq!(eval("builtins.typeOf 1"), Value::String("int".to_string()));
} }
#[test] #[test_log::test]
fn typeof_float() { fn typeof_float() {
assert_eq!( assert_eq!(
eval("builtins.typeOf 1.0"), eval("builtins.typeOf 1.0"),
@@ -60,17 +61,17 @@ fn typeof_float() {
); );
} }
#[test] #[test_log::test]
fn int_literal() { fn int_literal() {
assert_eq!(eval("1"), Value::Int(1)); assert_eq!(eval("1"), Value::Int(1));
} }
#[test] #[test_log::test]
fn float_literal() { fn float_literal() {
assert_eq!(eval("1."), Value::Float(1.)); assert_eq!(eval("1."), Value::Float(1.));
} }
#[test] #[test_log::test]
fn int_plus_int() { fn int_plus_int() {
assert_eq!( assert_eq!(
eval("builtins.typeOf (1 + 2)"), eval("builtins.typeOf (1 + 2)"),
@@ -78,7 +79,7 @@ fn int_plus_int() {
); );
} }
#[test] #[test_log::test]
fn int_plus_float() { fn int_plus_float() {
assert_eq!( assert_eq!(
eval("builtins.typeOf (1 + 2.0)"), eval("builtins.typeOf (1 + 2.0)"),
@@ -86,7 +87,7 @@ fn int_plus_float() {
); );
} }
#[test] #[test_log::test]
fn int_times_int() { fn int_times_int() {
assert_eq!( assert_eq!(
eval("builtins.typeOf (3 * 4)"), eval("builtins.typeOf (3 * 4)"),
@@ -94,7 +95,7 @@ fn int_times_int() {
); );
} }
#[test] #[test_log::test]
fn int_times_float() { fn int_times_float() {
assert_eq!( assert_eq!(
eval("builtins.typeOf (3 * 4.0)"), eval("builtins.typeOf (3 * 4.0)"),
@@ -102,25 +103,25 @@ fn int_times_float() {
); );
} }
#[test] #[test_log::test]
fn integer_division() { fn integer_division() {
assert_eq!(eval("5 / 2"), Value::Int(2)); assert_eq!(eval("5 / 2"), Value::Int(2));
assert_eq!(eval("7 / 3"), Value::Int(2)); assert_eq!(eval("7 / 3"), Value::Int(2));
assert_eq!(eval("10 / 3"), Value::Int(3)); assert_eq!(eval("10 / 3"), Value::Int(3));
} }
#[test] #[test_log::test]
fn float_division() { fn float_division() {
assert_eq!(eval("5 / 2.0"), Value::Float(2.5)); assert_eq!(eval("5 / 2.0"), Value::Float(2.5));
assert_eq!(eval("7.0 / 2"), Value::Float(3.5)); assert_eq!(eval("7.0 / 2"), Value::Float(3.5));
} }
#[test] #[test_log::test]
fn negative_integer_division() { fn negative_integer_division() {
assert_eq!(eval("(-7) / 3"), Value::Int(-2)); assert_eq!(eval("(-7) / 3"), Value::Int(-2));
} }
#[test] #[test_log::test]
fn builtin_add_with_large_numbers() { fn builtin_add_with_large_numbers() {
assert_eq!( assert_eq!(
eval("builtins.add 5000000000000000000 3000000000000000000"), eval("builtins.add 5000000000000000000 3000000000000000000"),
@@ -128,7 +129,7 @@ fn builtin_add_with_large_numbers() {
); );
} }
#[test] #[test_log::test]
fn builtin_mul_with_large_numbers() { fn builtin_mul_with_large_numbers() {
assert_eq!( assert_eq!(
eval("builtins.mul 1000000000 1000000000"), eval("builtins.mul 1000000000 1000000000"),

View File

@@ -1,68 +1,70 @@
use crate::utils::eval;
use nix_js::value::{AttrSet, List, Symbol, Value};
use std::collections::BTreeMap; use std::collections::BTreeMap;
#[test] use nix_js::value::{AttrSet, List, Symbol, Value};
use crate::utils::eval;
#[test_log::test]
fn addition() { fn addition() {
assert_eq!(eval("1 + 1"), Value::Int(2)); assert_eq!(eval("1 + 1"), Value::Int(2));
} }
#[test] #[test_log::test]
fn subtraction() { fn subtraction() {
assert_eq!(eval("2 - 1"), Value::Int(1)); assert_eq!(eval("2 - 1"), Value::Int(1));
} }
#[test] #[test_log::test]
fn multiplication() { fn multiplication() {
assert_eq!(eval("1. * 1"), Value::Float(1.)); assert_eq!(eval("1. * 1"), Value::Float(1.));
} }
#[test] #[test_log::test]
fn division() { fn division() {
assert_eq!(eval("1 / 1."), Value::Float(1.)); assert_eq!(eval("1 / 1."), Value::Float(1.));
} }
#[test] #[test_log::test]
fn equality() { fn equality() {
assert_eq!(eval("1 == 1"), Value::Bool(true)); assert_eq!(eval("1 == 1"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn inequality() { fn inequality() {
assert_eq!(eval("1 != 1"), Value::Bool(false)); assert_eq!(eval("1 != 1"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn less_than() { fn less_than() {
assert_eq!(eval("2 < 1"), Value::Bool(false)); assert_eq!(eval("2 < 1"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn greater_than() { fn greater_than() {
assert_eq!(eval("2 > 1"), Value::Bool(true)); assert_eq!(eval("2 > 1"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn less_than_or_equal() { fn less_than_or_equal() {
assert_eq!(eval("1 <= 1"), Value::Bool(true)); assert_eq!(eval("1 <= 1"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn greater_than_or_equal() { fn greater_than_or_equal() {
assert_eq!(eval("1 >= 1"), Value::Bool(true)); assert_eq!(eval("1 >= 1"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn logical_or_short_circuit() { fn logical_or_short_circuit() {
assert_eq!(eval("true || (1 / 0)"), Value::Bool(true)); assert_eq!(eval("true || (1 / 0)"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn logical_and() { fn logical_and() {
assert_eq!(eval("true && 1 == 0"), Value::Bool(false)); assert_eq!(eval("true && 1 == 0"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn list_concatenation() { fn list_concatenation() {
assert_eq!( assert_eq!(
eval("[ 1 2 3 ] ++ [ 4 5 6 ]"), eval("[ 1 2 3 ] ++ [ 4 5 6 ]"),
@@ -70,7 +72,7 @@ fn list_concatenation() {
); );
} }
#[test] #[test_log::test]
fn attrset_update() { fn attrset_update() {
assert_eq!( assert_eq!(
eval("{ a.b = 1; b = 2; } // { a.c = 2; }"), eval("{ a.b = 1; b = 2; } // { a.c = 2; }"),
@@ -87,23 +89,23 @@ fn attrset_update() {
); );
} }
#[test] #[test_log::test]
fn unary_negation() { fn unary_negation() {
assert_eq!(eval("-5"), Value::Int(-5)); assert_eq!(eval("-5"), Value::Int(-5));
} }
#[test] #[test_log::test]
fn logical_not() { fn logical_not() {
assert_eq!(eval("!true"), Value::Bool(false)); assert_eq!(eval("!true"), Value::Bool(false));
assert_eq!(eval("!false"), Value::Bool(true)); assert_eq!(eval("!false"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn select_with_default_lazy_evaluation() { fn select_with_default_lazy_evaluation() {
assert_eq!(eval("{ a = 1; }.a or (1 / 0)"), Value::Int(1)); assert_eq!(eval("{ a = 1; }.a or (1 / 0)"), Value::Int(1));
} }
#[test] #[test_log::test]
fn select_with_default_nested_lazy() { fn select_with_default_nested_lazy() {
assert_eq!( assert_eq!(
eval("{ a.b = 42; }.a.b or (builtins.abort \"should not evaluate\")"), eval("{ a.b = 42; }.a.b or (builtins.abort \"should not evaluate\")"),
@@ -111,32 +113,32 @@ fn select_with_default_nested_lazy() {
); );
} }
#[test] #[test_log::test]
fn select_with_default_fallback() { fn select_with_default_fallback() {
assert_eq!(eval("{ a = 1; }.b or 999"), Value::Int(999)); assert_eq!(eval("{ a = 1; }.b or 999"), Value::Int(999));
} }
#[test] #[test_log::test]
fn implication_false_false() { fn implication_false_false() {
assert_eq!(eval("false -> false"), Value::Bool(true)); assert_eq!(eval("false -> false"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn implication_false_true() { fn implication_false_true() {
assert_eq!(eval("false -> true"), Value::Bool(true)); assert_eq!(eval("false -> true"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn implication_true_false() { fn implication_true_false() {
assert_eq!(eval("true -> false"), Value::Bool(false)); assert_eq!(eval("true -> false"), Value::Bool(false));
} }
#[test] #[test_log::test]
fn implication_true_true() { fn implication_true_true() {
assert_eq!(eval("true -> true"), Value::Bool(true)); assert_eq!(eval("true -> true"), Value::Bool(true));
} }
#[test] #[test_log::test]
fn implication_short_circuit() { fn implication_short_circuit() {
assert_eq!(eval("false -> (1 / 0)"), Value::Bool(true)); assert_eq!(eval("false -> (1 / 0)"), Value::Bool(true));
} }

View File

@@ -1,113 +1,114 @@
use crate::utils::{eval, eval_result};
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::{eval, eval_result};
#[test_log::test]
fn path_type_of() { fn path_type_of() {
let result = eval("builtins.typeOf ./foo"); let result = eval("builtins.typeOf ./foo");
assert_eq!(result, Value::String("path".to_string())); assert_eq!(result, Value::String("path".to_string()));
} }
#[test] #[test_log::test]
fn is_path_true() { fn is_path_true() {
let result = eval("builtins.isPath ./foo"); let result = eval("builtins.isPath ./foo");
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn is_path_false_string() { fn is_path_false_string() {
let result = eval(r#"builtins.isPath "./foo""#); let result = eval(r#"builtins.isPath "./foo""#);
assert_eq!(result, Value::Bool(false)); assert_eq!(result, Value::Bool(false));
} }
#[test] #[test_log::test]
fn is_path_false_number() { fn is_path_false_number() {
let result = eval("builtins.isPath 42"); let result = eval("builtins.isPath 42");
assert_eq!(result, Value::Bool(false)); assert_eq!(result, Value::Bool(false));
} }
#[test] #[test_log::test]
fn path_concat_type() { fn path_concat_type() {
// path + string = path // path + string = path
let result = eval(r#"builtins.typeOf (./foo + "/bar")"#); let result = eval(r#"builtins.typeOf (./foo + "/bar")"#);
assert_eq!(result, Value::String("path".to_string())); assert_eq!(result, Value::String("path".to_string()));
} }
#[test] #[test_log::test]
fn string_path_concat_type() { fn string_path_concat_type() {
// string + path = string // string + path = string
let result = eval(r#"builtins.typeOf ("prefix-" + ./foo)"#); let result = eval(r#"builtins.typeOf ("prefix-" + ./foo)"#);
assert_eq!(result, Value::String("string".to_string())); assert_eq!(result, Value::String("string".to_string()));
} }
#[test] #[test_log::test]
fn basename_of_path() { fn basename_of_path() {
let result = eval("builtins.baseNameOf ./path/to/file.nix"); let result = eval("builtins.baseNameOf ./path/to/file.nix");
assert!(matches!(result, Value::String(s) if s == "file.nix")); assert!(matches!(result, Value::String(s) if s == "file.nix"));
} }
#[test] #[test_log::test]
fn basename_of_string() { fn basename_of_string() {
let result = eval(r#"builtins.baseNameOf "/path/to/file.nix""#); let result = eval(r#"builtins.baseNameOf "/path/to/file.nix""#);
assert_eq!(result, Value::String("file.nix".to_string())); assert_eq!(result, Value::String("file.nix".to_string()));
} }
#[test] #[test_log::test]
fn dir_of_path_type() { fn dir_of_path_type() {
// dirOf preserves path type // dirOf preserves path type
let result = eval("builtins.typeOf (builtins.dirOf ./path/to/file.nix)"); let result = eval("builtins.typeOf (builtins.dirOf ./path/to/file.nix)");
assert_eq!(result, Value::String("path".to_string())); assert_eq!(result, Value::String("path".to_string()));
} }
#[test] #[test_log::test]
fn dir_of_string_type() { fn dir_of_string_type() {
// dirOf preserves string type // dirOf preserves string type
let result = eval(r#"builtins.typeOf (builtins.dirOf "/path/to/file.nix")"#); let result = eval(r#"builtins.typeOf (builtins.dirOf "/path/to/file.nix")"#);
assert_eq!(result, Value::String("string".to_string())); assert_eq!(result, Value::String("string".to_string()));
} }
#[test] #[test_log::test]
fn path_equality() { fn path_equality() {
// Same path should be equal // Same path should be equal
let result = eval("./foo == ./foo"); let result = eval("./foo == ./foo");
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn path_not_equal_string() { fn path_not_equal_string() {
// Paths and strings are different types - should not be equal // Paths and strings are different types - should not be equal
let result = eval(r#"./foo == "./foo""#); let result = eval(r#"./foo == "./foo""#);
assert_eq!(result, Value::Bool(false)); assert_eq!(result, Value::Bool(false));
} }
#[test] #[test_log::test]
fn to_path_absolute() { fn to_path_absolute() {
// toPath with absolute path returns string // toPath with absolute path returns string
let result = eval(r#"builtins.toPath "/foo/bar""#); let result = eval(r#"builtins.toPath "/foo/bar""#);
assert_eq!(result, Value::String("/foo/bar".to_string())); assert_eq!(result, Value::String("/foo/bar".to_string()));
} }
#[test] #[test_log::test]
fn to_path_type_is_string() { fn to_path_type_is_string() {
// toPath returns a string, not a path // toPath returns a string, not a path
let result = eval(r#"builtins.typeOf (builtins.toPath "/foo")"#); let result = eval(r#"builtins.typeOf (builtins.toPath "/foo")"#);
assert_eq!(result, Value::String("string".to_string())); assert_eq!(result, Value::String("string".to_string()));
} }
#[test] #[test_log::test]
fn to_path_relative_fails() { fn to_path_relative_fails() {
// toPath with relative path should fail // toPath with relative path should fail
let result = eval_result(r#"builtins.toPath "foo/bar""#); let result = eval_result(r#"builtins.toPath "foo/bar""#);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn to_path_empty_fails() { fn to_path_empty_fails() {
// toPath with empty string should fail // toPath with empty string should fail
let result = eval_result(r#"builtins.toPath """#); let result = eval_result(r#"builtins.toPath """#);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn to_path_from_path_value() { fn to_path_from_path_value() {
// toPath can accept a path value too (coerces to string first) // toPath can accept a path value too (coerces to string first)
let result = eval("builtins.toPath ./foo"); let result = eval("builtins.toPath ./foo");

View File

@@ -1,9 +1,9 @@
use crate::utils::eval;
use nix_js::value::{List, Value}; use nix_js::value::{List, Value};
use crate::utils::eval;
use crate::utils::eval_result; use crate::utils::eval_result;
#[test] #[test_log::test]
fn match_exact_full_string() { fn match_exact_full_string() {
assert_eq!( assert_eq!(
eval(r#"builtins.match "foobar" "foobar""#), eval(r#"builtins.match "foobar" "foobar""#),
@@ -11,12 +11,12 @@ fn match_exact_full_string() {
); );
} }
#[test] #[test_log::test]
fn match_partial_returns_null() { fn match_partial_returns_null() {
assert_eq!(eval(r#"builtins.match "foo" "foobar""#), Value::Null); assert_eq!(eval(r#"builtins.match "foo" "foobar""#), Value::Null);
} }
#[test] #[test_log::test]
fn match_with_capture_groups() { fn match_with_capture_groups() {
assert_eq!( assert_eq!(
eval(r#"builtins.match "(.*)\\.nix" "foobar.nix""#), eval(r#"builtins.match "(.*)\\.nix" "foobar.nix""#),
@@ -24,7 +24,7 @@ fn match_with_capture_groups() {
); );
} }
#[test] #[test_log::test]
fn match_multiple_capture_groups() { fn match_multiple_capture_groups() {
assert_eq!( assert_eq!(
eval(r#"builtins.match "((.*)/)?([^/]*)\\.nix" "foobar.nix""#), eval(r#"builtins.match "((.*)/)?([^/]*)\\.nix" "foobar.nix""#),
@@ -36,7 +36,7 @@ fn match_multiple_capture_groups() {
); );
} }
#[test] #[test_log::test]
fn match_with_path() { fn match_with_path() {
assert_eq!( assert_eq!(
eval(r#"builtins.match "((.*)/)?([^/]*)\\.nix" "/path/to/foobar.nix""#), eval(r#"builtins.match "((.*)/)?([^/]*)\\.nix" "/path/to/foobar.nix""#),
@@ -48,7 +48,7 @@ fn match_with_path() {
); );
} }
#[test] #[test_log::test]
fn match_posix_space_class() { fn match_posix_space_class() {
assert_eq!( assert_eq!(
eval(r#"builtins.match "[[:space:]]+([^[:space:]]+)[[:space:]]+" " foo ""#), eval(r#"builtins.match "[[:space:]]+([^[:space:]]+)[[:space:]]+" " foo ""#),
@@ -56,7 +56,7 @@ fn match_posix_space_class() {
); );
} }
#[test] #[test_log::test]
fn match_posix_upper_class() { fn match_posix_upper_class() {
assert_eq!( assert_eq!(
eval(r#"builtins.match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " foo ""#), eval(r#"builtins.match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " foo ""#),
@@ -69,7 +69,7 @@ fn match_posix_upper_class() {
); );
} }
#[test] #[test_log::test]
fn match_quantifiers() { fn match_quantifiers() {
assert_eq!( assert_eq!(
eval(r#"builtins.match "fo*" "f""#), eval(r#"builtins.match "fo*" "f""#),
@@ -86,7 +86,7 @@ fn match_quantifiers() {
assert_eq!(eval(r#"builtins.match "fo{1,2}" "fooo""#), Value::Null); assert_eq!(eval(r#"builtins.match "fo{1,2}" "fooo""#), Value::Null);
} }
#[test] #[test_log::test]
fn split_non_capturing() { fn split_non_capturing() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "foobar" "foobar""#), eval(r#"builtins.split "foobar" "foobar""#),
@@ -98,7 +98,7 @@ fn split_non_capturing() {
); );
} }
#[test] #[test_log::test]
fn split_no_match() { fn split_no_match() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "fo+" "f""#), eval(r#"builtins.split "fo+" "f""#),
@@ -106,7 +106,7 @@ fn split_no_match() {
); );
} }
#[test] #[test_log::test]
fn split_with_capture_group() { fn split_with_capture_group() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "(fo*)" "foobar""#), eval(r#"builtins.split "(fo*)" "foobar""#),
@@ -118,7 +118,7 @@ fn split_with_capture_group() {
); );
} }
#[test] #[test_log::test]
fn split_multiple_matches() { fn split_multiple_matches() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "(b)" "foobarbaz""#), eval(r#"builtins.split "(b)" "foobarbaz""#),
@@ -132,7 +132,7 @@ fn split_multiple_matches() {
); );
} }
#[test] #[test_log::test]
fn split_with_multiple_groups() { fn split_with_multiple_groups() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "(f)(o*)" "foo""#), eval(r#"builtins.split "(f)(o*)" "foo""#),
@@ -147,7 +147,7 @@ fn split_with_multiple_groups() {
); );
} }
#[test] #[test_log::test]
fn split_with_optional_groups() { fn split_with_optional_groups() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "(a)|(c)" "abc""#), eval(r#"builtins.split "(a)|(c)" "abc""#),
@@ -161,7 +161,7 @@ fn split_with_optional_groups() {
); );
} }
#[test] #[test_log::test]
fn split_greedy_matching() { fn split_greedy_matching() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "(o+)" "oooofoooo""#), eval(r#"builtins.split "(o+)" "oooofoooo""#),
@@ -175,7 +175,7 @@ fn split_greedy_matching() {
); );
} }
#[test] #[test_log::test]
fn split_posix_classes() { fn split_posix_classes() {
assert_eq!( assert_eq!(
eval(r#"builtins.split "([[:upper:]]+)" " FOO ""#), eval(r#"builtins.split "([[:upper:]]+)" " FOO ""#),
@@ -187,7 +187,7 @@ fn split_posix_classes() {
); );
} }
#[test] #[test_log::test]
fn replace_basic() { fn replace_basic() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings ["o"] ["a"] "foobar""#), eval(r#"builtins.replaceStrings ["o"] ["a"] "foobar""#),
@@ -195,7 +195,7 @@ fn replace_basic() {
); );
} }
#[test] #[test_log::test]
fn replace_with_empty() { fn replace_with_empty() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings ["o"] [""] "foobar""#), eval(r#"builtins.replaceStrings ["o"] [""] "foobar""#),
@@ -203,7 +203,7 @@ fn replace_with_empty() {
); );
} }
#[test] #[test_log::test]
fn replace_multiple_patterns() { fn replace_multiple_patterns() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings ["oo" "a"] ["a" "oo"] "foobar""#), eval(r#"builtins.replaceStrings ["oo" "a"] ["a" "oo"] "foobar""#),
@@ -211,7 +211,7 @@ fn replace_multiple_patterns() {
); );
} }
#[test] #[test_log::test]
fn replace_first_match_wins() { fn replace_first_match_wins() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings ["oo" "oo"] ["u" "i"] "foobar""#), eval(r#"builtins.replaceStrings ["oo" "oo"] ["u" "i"] "foobar""#),
@@ -219,7 +219,7 @@ fn replace_first_match_wins() {
); );
} }
#[test] #[test_log::test]
fn replace_empty_pattern() { fn replace_empty_pattern() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings [""] ["X"] "abc""#), eval(r#"builtins.replaceStrings [""] ["X"] "abc""#),
@@ -227,7 +227,7 @@ fn replace_empty_pattern() {
); );
} }
#[test] #[test_log::test]
fn replace_empty_pattern_empty_string() { fn replace_empty_pattern_empty_string() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings [""] ["X"] """#), eval(r#"builtins.replaceStrings [""] ["X"] """#),
@@ -235,7 +235,7 @@ fn replace_empty_pattern_empty_string() {
); );
} }
#[test] #[test_log::test]
fn replace_simple_char() { fn replace_simple_char() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings ["-"] ["_"] "a-b""#), eval(r#"builtins.replaceStrings ["-"] ["_"] "a-b""#),
@@ -243,7 +243,7 @@ fn replace_simple_char() {
); );
} }
#[test] #[test_log::test]
fn replace_longer_pattern() { fn replace_longer_pattern() {
assert_eq!( assert_eq!(
eval(r#"builtins.replaceStrings ["oo"] ["u"] "foobar""#), eval(r#"builtins.replaceStrings ["oo"] ["u"] "foobar""#),
@@ -251,13 +251,13 @@ fn replace_longer_pattern() {
); );
} }
#[test] #[test_log::test]
fn replace_different_lengths() { fn replace_different_lengths() {
let result = eval_result(r#"builtins.replaceStrings ["a" "b"] ["x"] "test""#); let result = eval_result(r#"builtins.replaceStrings ["a" "b"] ["x"] "test""#);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn split_version_simple() { fn split_version_simple() {
assert_eq!( assert_eq!(
eval(r#"builtins.splitVersion "1.2.3""#), eval(r#"builtins.splitVersion "1.2.3""#),
@@ -269,7 +269,7 @@ fn split_version_simple() {
); );
} }
#[test] #[test_log::test]
fn split_version_with_pre() { fn split_version_with_pre() {
assert_eq!( assert_eq!(
eval(r#"builtins.splitVersion "2.3.0pre1234""#), eval(r#"builtins.splitVersion "2.3.0pre1234""#),
@@ -283,7 +283,7 @@ fn split_version_with_pre() {
); );
} }
#[test] #[test_log::test]
fn split_version_with_letters() { fn split_version_with_letters() {
assert_eq!( assert_eq!(
eval(r#"builtins.splitVersion "2.3a""#), eval(r#"builtins.splitVersion "2.3a""#),
@@ -295,7 +295,7 @@ fn split_version_with_letters() {
); );
} }
#[test] #[test_log::test]
fn split_version_with_dashes() { fn split_version_with_dashes() {
assert_eq!( assert_eq!(
eval(r#"builtins.splitVersion "2.3-beta1""#), eval(r#"builtins.splitVersion "2.3-beta1""#),
@@ -308,7 +308,7 @@ fn split_version_with_dashes() {
); );
} }
#[test] #[test_log::test]
fn split_version_empty() { fn split_version_empty() {
assert_eq!( assert_eq!(
eval(r#"builtins.splitVersion """#), eval(r#"builtins.splitVersion """#),

View File

@@ -1,18 +1,19 @@
use crate::utils::eval_result;
use nix_js::context::Context; use nix_js::context::Context;
use nix_js::value::Value; use nix_js::value::Value;
use crate::utils::eval_result;
fn eval(expr: &str) -> Value { fn eval(expr: &str) -> Value {
eval_result(expr).unwrap_or_else(|e| panic!("{}", e)) eval_result(expr).unwrap_or_else(|e| panic!("{}", e))
} }
#[test] #[test_log::test]
fn hascontext_plain_string() { fn hascontext_plain_string() {
let result = eval(r#"builtins.hasContext "hello""#); let result = eval(r#"builtins.hasContext "hello""#);
assert_eq!(result, Value::Bool(false)); assert_eq!(result, Value::Bool(false));
} }
#[test] #[test_log::test]
fn hascontext_derivation_output() { fn hascontext_derivation_output() {
let result = eval( let result = eval(
r#" r#"
@@ -24,7 +25,7 @@ fn hascontext_derivation_output() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn getcontext_plain_string() { fn getcontext_plain_string() {
let result = eval(r#"builtins.getContext "hello""#); let result = eval(r#"builtins.getContext "hello""#);
match result { match result {
@@ -35,7 +36,7 @@ fn getcontext_plain_string() {
} }
} }
#[test] #[test_log::test]
fn getcontext_derivation_output() { fn getcontext_derivation_output() {
let result = eval( let result = eval(
r#" r#"
@@ -60,7 +61,7 @@ fn getcontext_derivation_output() {
} }
} }
#[test] #[test_log::test]
fn unsafediscardstringcontext() { fn unsafediscardstringcontext() {
let result = eval( let result = eval(
r#" r#"
@@ -74,7 +75,7 @@ fn unsafediscardstringcontext() {
assert_eq!(result, Value::Bool(false)); assert_eq!(result, Value::Bool(false));
} }
#[test] #[test_log::test]
fn unsafediscardstringcontext_preserves_value() { fn unsafediscardstringcontext_preserves_value() {
let result = eval( let result = eval(
r#" r#"
@@ -88,7 +89,7 @@ fn unsafediscardstringcontext_preserves_value() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn appendcontext_basic() { fn appendcontext_basic() {
let result = eval( let result = eval(
r#" r#"
@@ -102,7 +103,7 @@ fn appendcontext_basic() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn appendcontext_preserves_value() { fn appendcontext_preserves_value() {
let result = eval( let result = eval(
r#" r#"
@@ -116,7 +117,7 @@ fn appendcontext_preserves_value() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn string_concat_merges_context() { fn string_concat_merges_context() {
let result = eval( let result = eval(
r#" r#"
@@ -133,7 +134,7 @@ fn string_concat_merges_context() {
assert_eq!(result, Value::Int(2)); assert_eq!(result, Value::Int(2));
} }
#[test] #[test_log::test]
fn string_add_merges_context() { fn string_add_merges_context() {
let result = eval( let result = eval(
r#" r#"
@@ -150,7 +151,7 @@ fn string_add_merges_context() {
assert_eq!(result, Value::Int(2)); assert_eq!(result, Value::Int(2));
} }
#[test] #[test_log::test]
fn context_in_derivation_args() { fn context_in_derivation_args() {
let mut ctx = Context::new().unwrap(); let mut ctx = Context::new().unwrap();
let result = ctx let result = ctx
@@ -179,7 +180,7 @@ fn context_in_derivation_args() {
} }
} }
#[test] #[test_log::test]
fn context_in_derivation_env() { fn context_in_derivation_env() {
let mut ctx = Context::new().unwrap(); let mut ctx = Context::new().unwrap();
let result = ctx let result = ctx
@@ -208,7 +209,7 @@ fn context_in_derivation_env() {
} }
} }
#[test] #[test_log::test]
fn tostring_preserves_context() { fn tostring_preserves_context() {
let result = eval( let result = eval(
r#" r#"
@@ -221,7 +222,7 @@ fn tostring_preserves_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn interpolation_derivation_returns_outpath() { fn interpolation_derivation_returns_outpath() {
let mut ctx = Context::new().unwrap(); let mut ctx = Context::new().unwrap();
let result = ctx let result = ctx
@@ -244,7 +245,7 @@ fn interpolation_derivation_returns_outpath() {
} }
} }
#[test] #[test_log::test]
fn interpolation_derivation_has_context() { fn interpolation_derivation_has_context() {
let result = eval( let result = eval(
r#" r#"
@@ -256,7 +257,7 @@ fn interpolation_derivation_has_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn interpolation_derivation_context_correct() { fn interpolation_derivation_context_correct() {
let result = eval( let result = eval(
r#" r#"
@@ -277,7 +278,7 @@ fn interpolation_derivation_context_correct() {
} }
} }
#[test] #[test_log::test]
fn interpolation_multiple_derivations() { fn interpolation_multiple_derivations() {
let result = eval( let result = eval(
r#" r#"
@@ -292,7 +293,7 @@ fn interpolation_multiple_derivations() {
assert_eq!(result, Value::Int(2)); assert_eq!(result, Value::Int(2));
} }
#[test] #[test_log::test]
fn interpolation_derivation_equals_tostring() { fn interpolation_derivation_equals_tostring() {
let result = eval( let result = eval(
r#" r#"
@@ -304,7 +305,7 @@ fn interpolation_derivation_equals_tostring() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn substring_preserves_context() { fn substring_preserves_context() {
let result = eval( let result = eval(
r#" r#"
@@ -318,7 +319,7 @@ fn substring_preserves_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn substring_zero_length_preserves_context() { fn substring_zero_length_preserves_context() {
let result = eval( let result = eval(
r#" r#"
@@ -332,7 +333,7 @@ fn substring_zero_length_preserves_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn substring_zero_length_empty_value() { fn substring_zero_length_empty_value() {
let result = eval( let result = eval(
r#" r#"
@@ -346,7 +347,7 @@ fn substring_zero_length_empty_value() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn concatStringsSep_preserves_context() { fn concatStringsSep_preserves_context() {
let result = eval( let result = eval(
@@ -363,7 +364,7 @@ fn concatStringsSep_preserves_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn concatStringsSep_merges_contexts() { fn concatStringsSep_merges_contexts() {
let result = eval( let result = eval(
@@ -381,7 +382,7 @@ fn concatStringsSep_merges_contexts() {
assert_eq!(result, Value::Int(2)); assert_eq!(result, Value::Int(2));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn concatStringsSep_separator_has_context() { fn concatStringsSep_separator_has_context() {
let result = eval( let result = eval(
@@ -396,7 +397,7 @@ fn concatStringsSep_separator_has_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn replaceStrings_input_context_preserved() { fn replaceStrings_input_context_preserved() {
let result = eval( let result = eval(
@@ -411,7 +412,7 @@ fn replaceStrings_input_context_preserved() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn replaceStrings_replacement_context_collected() { fn replaceStrings_replacement_context_collected() {
let result = eval( let result = eval(
@@ -426,7 +427,7 @@ fn replaceStrings_replacement_context_collected() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn replaceStrings_merges_contexts() { fn replaceStrings_merges_contexts() {
let result = eval( let result = eval(
@@ -444,7 +445,7 @@ fn replaceStrings_merges_contexts() {
assert_eq!(result, Value::Int(2)); assert_eq!(result, Value::Int(2));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn replaceStrings_lazy_evaluation_context() { fn replaceStrings_lazy_evaluation_context() {
let result = eval( let result = eval(
@@ -459,7 +460,7 @@ fn replaceStrings_lazy_evaluation_context() {
assert_eq!(result, Value::Bool(false)); assert_eq!(result, Value::Bool(false));
} }
#[test] #[test_log::test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn baseNameOf_preserves_context() { fn baseNameOf_preserves_context() {
let result = eval( let result = eval(
@@ -474,7 +475,7 @@ fn baseNameOf_preserves_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn split_no_match_preserves_context() { fn split_no_match_preserves_context() {
let result = eval( let result = eval(
r#" r#"
@@ -488,7 +489,7 @@ fn split_no_match_preserves_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn builtins_path_has_context() { fn builtins_path_has_context() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt"); let test_file = temp_dir.path().join("test.txt");
@@ -502,7 +503,7 @@ fn builtins_path_has_context() {
assert_eq!(result, Value::Bool(true)); assert_eq!(result, Value::Bool(true));
} }
#[test] #[test_log::test]
fn builtins_path_context_tracked_in_structured_attrs_derivation() { fn builtins_path_context_tracked_in_structured_attrs_derivation() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test-patch.txt"); let test_file = temp_dir.path().join("test-patch.txt");
@@ -532,7 +533,7 @@ fn builtins_path_context_tracked_in_structured_attrs_derivation() {
} }
} }
#[test] #[test_log::test]
fn builtins_path_context_tracked_in_non_structured_derivation() { fn builtins_path_context_tracked_in_non_structured_derivation() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("dep.txt"); let test_file = temp_dir.path().join("dep.txt");

View File

@@ -1,12 +1,13 @@
use crate::utils::eval;
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::eval;
#[test_log::test]
fn non_recursive_bindings() { fn non_recursive_bindings() {
assert_eq!(eval("let x = 1; y = 2; z = x + y; in z"), Value::Int(3)); assert_eq!(eval("let x = 1; y = 2; z = x + y; in z"), Value::Int(3));
} }
#[test] #[test_log::test]
fn non_recursive_multiple_bindings() { fn non_recursive_multiple_bindings() {
assert_eq!( assert_eq!(
eval("let a = 10; b = 20; c = 30; d = a + b + c; in d"), eval("let a = 10; b = 20; c = 30; d = a + b + c; in d"),
@@ -14,7 +15,7 @@ fn non_recursive_multiple_bindings() {
); );
} }
#[test] #[test_log::test]
fn recursive_fibonacci() { fn recursive_fibonacci() {
assert_eq!( assert_eq!(
eval("let fib = n: if n <= 1 then 1 else fib (n - 1) + fib (n - 2); in fib 5"), eval("let fib = n: if n <= 1 then 1 else fib (n - 1) + fib (n - 2); in fib 5"),
@@ -22,7 +23,7 @@ fn recursive_fibonacci() {
); );
} }
#[test] #[test_log::test]
fn recursive_factorial() { fn recursive_factorial() {
assert_eq!( assert_eq!(
eval("let factorial = n: if n == 0 then 1 else n * factorial (n - 1); in factorial 5"), eval("let factorial = n: if n == 0 then 1 else n * factorial (n - 1); in factorial 5"),
@@ -30,7 +31,7 @@ fn recursive_factorial() {
); );
} }
#[test] #[test_log::test]
fn mutual_recursion_simple() { fn mutual_recursion_simple() {
assert_eq!( assert_eq!(
eval( eval(
@@ -40,7 +41,7 @@ fn mutual_recursion_simple() {
); );
} }
#[test] #[test_log::test]
fn mutual_recursion_even_odd() { fn mutual_recursion_even_odd() {
assert_eq!( assert_eq!(
eval( eval(
@@ -50,7 +51,7 @@ fn mutual_recursion_even_odd() {
); );
} }
#[test] #[test_log::test]
fn mixed_recursive_and_non_recursive() { fn mixed_recursive_and_non_recursive() {
assert_eq!( assert_eq!(
eval("let x = 1; f = n: if n == 0 then x else f (n - 1); in f 5"), eval("let x = 1; f = n: if n == 0 then x else f (n - 1); in f 5"),
@@ -58,7 +59,7 @@ fn mixed_recursive_and_non_recursive() {
); );
} }
#[test] #[test_log::test]
fn mixed_with_multiple_non_recursive() { fn mixed_with_multiple_non_recursive() {
assert_eq!( assert_eq!(
eval( eval(
@@ -68,12 +69,12 @@ fn mixed_with_multiple_non_recursive() {
); );
} }
#[test] #[test_log::test]
fn rec_attrset_non_recursive() { fn rec_attrset_non_recursive() {
assert_eq!(eval("rec { x = 1; y = 2; z = x + y; }.z"), Value::Int(3)); assert_eq!(eval("rec { x = 1; y = 2; z = x + y; }.z"), Value::Int(3));
} }
#[test] #[test_log::test]
fn rec_attrset_recursive() { fn rec_attrset_recursive() {
assert_eq!( assert_eq!(
eval("rec { f = n: if n == 0 then 0 else f (n - 1); }.f 10"), eval("rec { f = n: if n == 0 then 0 else f (n - 1); }.f 10"),
@@ -81,7 +82,7 @@ fn rec_attrset_recursive() {
); );
} }
#[test] #[test_log::test]
fn nested_let_non_recursive() { fn nested_let_non_recursive() {
assert_eq!( assert_eq!(
eval("let x = 1; in let y = x + 1; z = y + 1; in z"), eval("let x = 1; in let y = x + 1; z = y + 1; in z"),
@@ -89,7 +90,7 @@ fn nested_let_non_recursive() {
); );
} }
#[test] #[test_log::test]
fn nested_let_with_recursive() { fn nested_let_with_recursive() {
assert_eq!( assert_eq!(
eval("let f = n: if n == 0 then 0 else f (n - 1); in let g = m: f m; in g 5"), eval("let f = n: if n == 0 then 0 else f (n - 1); in let g = m: f m; in g 5"),
@@ -97,7 +98,7 @@ fn nested_let_with_recursive() {
); );
} }
#[test] #[test_log::test]
fn three_way_mutual_recursion() { fn three_way_mutual_recursion() {
assert_eq!( assert_eq!(
eval( eval(
@@ -107,7 +108,7 @@ fn three_way_mutual_recursion() {
); );
} }
#[test] #[test_log::test]
fn complex_mixed_dependencies() { fn complex_mixed_dependencies() {
assert_eq!( assert_eq!(
eval( eval(

View File

@@ -1,7 +1,8 @@
use crate::utils::{eval, eval_result};
use nix_js::value::Value; use nix_js::value::Value;
#[test] use crate::utils::{eval, eval_result};
#[test_log::test]
fn string_returns_as_is() { fn string_returns_as_is() {
assert_eq!( assert_eq!(
eval(r#"toString "hello""#), eval(r#"toString "hello""#),
@@ -9,32 +10,32 @@ fn string_returns_as_is() {
); );
} }
#[test] #[test_log::test]
fn integer_to_string() { fn integer_to_string() {
assert_eq!(eval("toString 42"), Value::String("42".to_string())); assert_eq!(eval("toString 42"), Value::String("42".to_string()));
assert_eq!(eval("toString (-5)"), Value::String("-5".to_string())); assert_eq!(eval("toString (-5)"), Value::String("-5".to_string()));
assert_eq!(eval("toString 0"), Value::String("0".to_string())); assert_eq!(eval("toString 0"), Value::String("0".to_string()));
} }
#[test] #[test_log::test]
fn float_to_string() { fn float_to_string() {
assert_eq!(eval("toString 3.14"), Value::String("3.14".to_string())); assert_eq!(eval("toString 3.14"), Value::String("3.14".to_string()));
assert_eq!(eval("toString 0.0"), Value::String("0".to_string())); assert_eq!(eval("toString 0.0"), Value::String("0".to_string()));
assert_eq!(eval("toString (-2.5)"), Value::String("-2.5".to_string())); assert_eq!(eval("toString (-2.5)"), Value::String("-2.5".to_string()));
} }
#[test] #[test_log::test]
fn bool_to_string() { fn bool_to_string() {
assert_eq!(eval("toString true"), Value::String("1".to_string())); assert_eq!(eval("toString true"), Value::String("1".to_string()));
assert_eq!(eval("toString false"), Value::String("".to_string())); assert_eq!(eval("toString false"), Value::String("".to_string()));
} }
#[test] #[test_log::test]
fn null_to_string() { fn null_to_string() {
assert_eq!(eval("toString null"), Value::String("".to_string())); assert_eq!(eval("toString null"), Value::String("".to_string()));
} }
#[test] #[test_log::test]
fn simple_list_to_string() { fn simple_list_to_string() {
assert_eq!(eval("toString [1 2 3]"), Value::String("1 2 3".to_string())); assert_eq!(eval("toString [1 2 3]"), Value::String("1 2 3".to_string()));
assert_eq!( assert_eq!(
@@ -43,7 +44,7 @@ fn simple_list_to_string() {
); );
} }
#[test] #[test_log::test]
fn nested_list_flattens() { fn nested_list_flattens() {
assert_eq!( assert_eq!(
eval("toString [[1 2] [3 4]]"), eval("toString [[1 2] [3 4]]"),
@@ -55,14 +56,14 @@ fn nested_list_flattens() {
); );
} }
#[test] #[test_log::test]
fn empty_list_in_list_no_extra_space() { fn empty_list_in_list_no_extra_space() {
assert_eq!(eval("toString [1 [] 2]"), Value::String("1 2".to_string())); assert_eq!(eval("toString [1 [] 2]"), Value::String("1 2".to_string()));
assert_eq!(eval("toString [[] 1 2]"), Value::String("1 2".to_string())); assert_eq!(eval("toString [[] 1 2]"), Value::String("1 2".to_string()));
assert_eq!(eval("toString [1 2 []]"), Value::String("1 2 ".to_string())); assert_eq!(eval("toString [1 2 []]"), Value::String("1 2 ".to_string()));
} }
#[test] #[test_log::test]
fn list_with_multiple_empty_lists() { fn list_with_multiple_empty_lists() {
assert_eq!( assert_eq!(
eval("toString [1 [] [] 2]"), eval("toString [1 [] [] 2]"),
@@ -71,7 +72,7 @@ fn list_with_multiple_empty_lists() {
assert_eq!(eval("toString [[] [] 1]"), Value::String("1".to_string())); assert_eq!(eval("toString [[] [] 1]"), Value::String("1".to_string()));
} }
#[test] #[test_log::test]
fn list_with_bool_and_null() { fn list_with_bool_and_null() {
assert_eq!( assert_eq!(
eval("toString [true false null]"), eval("toString [true false null]"),
@@ -83,7 +84,7 @@ fn list_with_bool_and_null() {
); );
} }
#[test] #[test_log::test]
fn mixed_type_list() { fn mixed_type_list() {
assert_eq!( assert_eq!(
eval(r#"toString [1 "hello" 2.5 true]"#), eval(r#"toString [1 "hello" 2.5 true]"#),
@@ -91,7 +92,7 @@ fn mixed_type_list() {
); );
} }
#[test] #[test_log::test]
fn attrs_with_out_path() { fn attrs_with_out_path() {
assert_eq!( assert_eq!(
eval(r#"toString { outPath = "/nix/store/foo"; }"#), eval(r#"toString { outPath = "/nix/store/foo"; }"#),
@@ -99,7 +100,7 @@ fn attrs_with_out_path() {
); );
} }
#[test] #[test_log::test]
fn attrs_with_to_string_method() { fn attrs_with_to_string_method() {
assert_eq!( assert_eq!(
eval(r#"toString { __toString = self: "custom"; }"#), eval(r#"toString { __toString = self: "custom"; }"#),
@@ -107,7 +108,7 @@ fn attrs_with_to_string_method() {
); );
} }
#[test] #[test_log::test]
fn attrs_to_string_self_reference() { fn attrs_to_string_self_reference() {
assert_eq!( assert_eq!(
eval( eval(
@@ -117,7 +118,7 @@ fn attrs_to_string_self_reference() {
); );
} }
#[test] #[test_log::test]
fn attrs_to_string_priority() { fn attrs_to_string_priority() {
assert_eq!( assert_eq!(
eval(r#"toString { __toString = self: "custom"; outPath = "/nix/store/foo"; }"#), eval(r#"toString { __toString = self: "custom"; outPath = "/nix/store/foo"; }"#),
@@ -125,7 +126,7 @@ fn attrs_to_string_priority() {
); );
} }
#[test] #[test_log::test]
fn derivation_like_object() { fn derivation_like_object() {
assert_eq!( assert_eq!(
eval( eval(
@@ -135,7 +136,7 @@ fn derivation_like_object() {
); );
} }
#[test] #[test_log::test]
fn string_interpolation_with_int() { fn string_interpolation_with_int() {
assert_eq!( assert_eq!(
eval(r#""value: ${toString 42}""#), eval(r#""value: ${toString 42}""#),
@@ -143,7 +144,7 @@ fn string_interpolation_with_int() {
); );
} }
#[test] #[test_log::test]
fn string_interpolation_with_list() { fn string_interpolation_with_list() {
assert_eq!( assert_eq!(
eval(r#""items: ${toString [1 2 3]}""#), eval(r#""items: ${toString [1 2 3]}""#),
@@ -151,7 +152,7 @@ fn string_interpolation_with_list() {
); );
} }
#[test] #[test_log::test]
fn nested_to_string_calls() { fn nested_to_string_calls() {
assert_eq!( assert_eq!(
eval(r#"toString (toString 42)"#), eval(r#"toString (toString 42)"#),
@@ -159,7 +160,7 @@ fn nested_to_string_calls() {
); );
} }
#[test] #[test_log::test]
fn to_string_in_let_binding() { fn to_string_in_let_binding() {
assert_eq!( assert_eq!(
eval(r#"let x = toString 42; y = toString 10; in "${x}-${y}""#), eval(r#"let x = toString 42; y = toString 10; in "${x}-${y}""#),
@@ -167,17 +168,17 @@ fn to_string_in_let_binding() {
); );
} }
#[test] #[test_log::test]
fn empty_string() { fn empty_string() {
assert_eq!(eval(r#"toString """#), Value::String("".to_string())); assert_eq!(eval(r#"toString """#), Value::String("".to_string()));
} }
#[test] #[test_log::test]
fn empty_list() { fn empty_list() {
assert_eq!(eval("toString []"), Value::String("".to_string())); assert_eq!(eval("toString []"), Value::String("".to_string()));
} }
#[test] #[test_log::test]
fn to_string_preserves_spaces_in_strings() { fn to_string_preserves_spaces_in_strings() {
assert_eq!( assert_eq!(
eval(r#"toString "hello world""#), eval(r#"toString "hello world""#),
@@ -185,7 +186,7 @@ fn to_string_preserves_spaces_in_strings() {
); );
} }
#[test] #[test_log::test]
fn list_of_empty_strings() { fn list_of_empty_strings() {
assert_eq!( assert_eq!(
eval(r#"toString ["" "" ""]"#), eval(r#"toString ["" "" ""]"#),
@@ -193,7 +194,7 @@ fn list_of_empty_strings() {
); );
} }
#[test] #[test_log::test]
fn deeply_nested_lists() { fn deeply_nested_lists() {
assert_eq!( assert_eq!(
eval("toString [[[1] [2]] [[3] [4]]]"), eval("toString [[[1] [2]] [[3] [4]]]"),
@@ -201,7 +202,7 @@ fn deeply_nested_lists() {
); );
} }
#[test] #[test_log::test]
fn list_with_nested_empty_lists() { fn list_with_nested_empty_lists() {
assert_eq!( assert_eq!(
eval("toString [1 [[]] 2]"), eval("toString [1 [[]] 2]"),
@@ -209,19 +210,19 @@ fn list_with_nested_empty_lists() {
); );
} }
#[test] #[test_log::test]
fn attrs_without_out_path_or_to_string_fails() { fn attrs_without_out_path_or_to_string_fails() {
let result = eval_result(r#"toString { foo = "bar"; }"#); let result = eval_result(r#"toString { foo = "bar"; }"#);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn function_to_string_fails() { fn function_to_string_fails() {
let result = eval_result("toString (x: x)"); let result = eval_result("toString (x: x)");
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test_log::test]
fn to_string_method_must_return_string() { fn to_string_method_must_return_string() {
assert_eq!( assert_eq!(
eval(r#"toString { __toString = self: 42; }"#), eval(r#"toString { __toString = self: 42; }"#),
@@ -233,7 +234,7 @@ fn to_string_method_must_return_string() {
); );
} }
#[test] #[test_log::test]
fn out_path_can_be_nested() { fn out_path_can_be_nested() {
assert_eq!( assert_eq!(
eval(r#"toString { outPath = { outPath = "/final/path"; }; }"#), eval(r#"toString { outPath = { outPath = "/final/path"; }; }"#),
@@ -241,7 +242,7 @@ fn out_path_can_be_nested() {
); );
} }
#[test] #[test_log::test]
fn list_spacing_matches_nix_behavior() { fn list_spacing_matches_nix_behavior() {
assert_eq!( assert_eq!(
eval(r#"toString ["a" "b"]"#), eval(r#"toString ["a" "b"]"#),

1
rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
group_imports = "StdExternalCrate"

View File

@@ -1,7 +1,7 @@
[files] [files]
extend-exclude = [ extend-exclude = [
"nix-js/tests/basic/regex.rs", "nix-js/tests/tests/regex.rs",
"nix-js/tests/lang", "nix-js/tests/tests/lang",
] ]
[default.extend-words] [default.extend-words]