Compare commits

1 Commits
main ... new

Author SHA1 Message Date
1083b8fdfa temp 2026-03-13 21:57:23 +08:00
10 changed files with 569 additions and 370 deletions

82
Cargo.lock generated
View File

@@ -41,6 +41,12 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "allocator-api2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
[[package]]
name = "anes"
version = "0.1.6"
@@ -276,6 +282,15 @@ dependencies = [
"syn",
]
[[package]]
name = "boxing"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a817f12ef805b34fe1565bea00630d84f8f08bf26200b05c41456c77cdada88"
dependencies = [
"sptr",
]
[[package]]
name = "bstr"
version = "1.12.1"
@@ -293,7 +308,7 @@ version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
dependencies = [
"allocator-api2",
"allocator-api2 0.2.21",
]
[[package]]
@@ -528,15 +543,6 @@ dependencies = [
"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]]
name = "cooked-waker"
version = "5.0.0"
@@ -847,7 +853,7 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case 0.10.0",
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
@@ -1261,6 +1267,30 @@ dependencies = [
"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]]
name = "generic-array"
version = "0.14.7"
@@ -1354,6 +1384,9 @@ name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"allocator-api2 0.2.21",
]
[[package]]
name = "hashbrown"
@@ -1370,7 +1403,7 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"allocator-api2 0.2.21",
"equivalent",
"foldhash 0.2.0",
]
@@ -2059,8 +2092,10 @@ dependencies = [
name = "nix-js"
version = "0.1.0"
dependencies = [
"allocator-api2 0.4.0",
"anyhow",
"base64 0.22.1",
"boxing",
"bumpalo",
"bzip2",
"clap",
@@ -2073,6 +2108,7 @@ dependencies = [
"ere",
"fastwebsockets",
"flate2",
"gc-arena",
"ghost-cell",
"hashbrown 0.16.1",
"hex",
@@ -2085,7 +2121,6 @@ dependencies = [
"miette",
"mimalloc",
"nix-compat",
"nix-js-macros",
"nix-nar",
"num_enum",
"regex",
@@ -2099,6 +2134,7 @@ dependencies = [
"serde_json",
"sha1",
"sha2",
"smallvec",
"string-interner",
"tap",
"tar",
@@ -2113,16 +2149,6 @@ dependencies = [
"xz2",
]
[[package]]
name = "nix-js-macros"
version = "0.1.0"
dependencies = [
"convert_case 0.11.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "nix-nar"
version = "0.3.1"
@@ -3193,6 +3219,12 @@ dependencies = [
"der",
]
[[package]]
name = "sptr"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
[[package]]
name = "sqlite-wasm-rs"
version = "0.5.2"
@@ -3612,9 +3644,9 @@ dependencies = [
[[package]]
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"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
checksum = "eb5238e643fc34a1d5d7e753e1532a91912d74b63b92b3ea51fde8d1b7bc79dd"
dependencies = [
"indexmap",
"serde_core",

View File

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

View File

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

View File

@@ -1,241 +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. `From` implementations to easily convert from a struct variant (e.g., `BinOp`) to the main enum (`Ir::BinOp`).
//! 4. 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::{
Expr, ExprPath, FieldsNamed, GenericArgument, GenericParam, Generics, Ident, Path, PathSegment,
Token, Type, TypePath, parenthesized,
parse::{Parse, ParseStream, Result},
punctuated::Punctuated,
token,
};
/// Represents one of the variants passed to the `ir!` macro.
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.
struct MacroInput {
/// The name of the main IR enum to be generated (e.g., `Ir`).
base_name: Ident,
generics: Generics,
/// The list of variants for the enum.
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> {
let base_name = input.parse()?;
let generics = Generics::parse(input)?;
input.parse::<Token![;]>()?;
let variants = Punctuated::parse_terminated(input)?;
Ok(MacroInput {
base_name,
generics,
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 generic_params = &parsed_input.generics.params;
let mk_ident_path = |ident| Path {
leading_colon: None,
segments: Punctuated::from_iter(std::iter::once(PathSegment {
ident,
arguments: Default::default(),
})),
};
let generic_args = {
generic_params
.iter()
.map(|arg| match arg {
GenericParam::Lifetime(lifetime) => {
GenericArgument::Lifetime(lifetime.lifetime.clone())
}
GenericParam::Const(cnst) => GenericArgument::Const(Expr::Path(ExprPath {
path: mk_ident_path(cnst.ident.clone()),
attrs: Vec::new(),
qself: None,
})),
GenericParam::Type(ty) => GenericArgument::Type(Type::Path(TypePath {
path: mk_ident_path(ty.ident.clone()),
qself: None,
})),
})
.collect::<Punctuated<_, Token![,]>>()
};
let where_clause = &parsed_input.generics.where_clause;
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 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) });
span_arms.push(quote! { Self::#name(inner) => inner.span });
from_impls.push(quote! {
impl <#generic_params> From<#inner_type> for #base_name <#generic_args> #where_clause {
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
}
});
to_trait_impls.push(quote! {
impl <#generic_params> #to_trait_name <#generic_args> for #name #where_clause {
fn #to_trait_fn_name(self) -> #base_name <#generic_args> { #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) });
span_arms.push(quote! { Self::#name(inner) => inner.span });
from_impls.push(quote! {
impl <#generic_params> From<#inner_type> for #base_name <#generic_args> #where_clause {
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
}
});
to_trait_impls.push(quote! {
impl <#generic_params> #to_trait_name <#generic_args> for #name #where_clause {
fn #to_trait_fn_name(self) -> #base_name <#generic_args> { #base_name::from(self) }
}
});
}
VariantInput::Struct(name, mut fields) => {
let inner_type = name.clone();
fields.named.iter_mut().for_each(|field| {
field.vis = syn::Visibility::Public(syn::token::Pub::default());
});
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 <#generic_params> #where_clause #fields
});
enum_variants.push(quote! { #name(#inner_type <#generic_args>) });
span_arms.push(quote! { Self::#name(inner) => inner.span });
from_impls.push(quote! {
impl <#generic_params> From<#inner_type <#generic_args>> for #base_name <#generic_args> #where_clause {
fn from(val: #inner_type <#generic_args>) -> Self { #base_name::#name(val) }
}
});
to_trait_impls.push(quote! {
impl <#generic_params> #to_trait_name <#generic_args> for #name <#generic_args> #where_clause {
fn #to_trait_fn_name(self) -> #base_name <#generic_args> { #base_name::from(self) }
}
});
}
}
}
// Assemble the final generated code.
let expanded = quote! {
/// The main IR enum, generated by the `ir!` macro.
#[derive(Debug)]
pub enum #base_name <#generic_params> #where_clause {
#( #enum_variants ),*
}
// The struct definitions for the enum variants.
#( #struct_defs )*
impl <#generic_params> #base_name <#generic_args> #where_clause {
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 <#generic_params> #where_clause {
/// Performs the conversion.
fn #to_trait_fn_name(self) -> #base_name <#generic_args>;
}
// 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"
version = "0.1.0"
edition = "2024"
build = "build.rs"
[dependencies]
mimalloc = "0.1"
@@ -54,7 +53,7 @@ bzip2 = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# spec 1.0.0
toml = "0.9.9"
toml = "=0.9.9"
dirs = "6.0"
tempfile = "3.24"
rusqlite = { version = "0.38", features = ["bundled"] }
@@ -62,7 +61,6 @@ rusqlite = { version = "0.38", features = ["bundled"] }
rnix = "0.14"
rowan = "0.16"
nix-js-macros = { path = "../nix-js-macros" }
ere = "0.2.4"
num_enum = "0.7.5"
tap = "1.0.1"
@@ -74,8 +72,13 @@ hyper-util = { version = "0.1", features = ["tokio"], optional = true }
http-body-util = { version = "0.1", optional = true }
http = { version = "1", optional = true }
uuid = { version = "1", features = ["v4"], optional = true }
ghost-cell = "0.2.6"
colored = "3.1.1"
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]
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]

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

@@ -9,12 +9,12 @@
"version": "0.1.0",
"dependencies": {
"globals": "^17.3.0",
"jiti": "^2.6.1",
"js-sdsl": "^4.4.2"
},
"devDependencies": {
"esbuild": "^0.24.2",
"eslint": "^9.39.2",
"jiti": "^2.6.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.55.0"
}
@@ -1518,6 +1518,7 @@
"version": "2.6.1",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {

View File

@@ -15,6 +15,7 @@ use crate::value::{AttrSet, List, Symbol, Value};
pub(crate) mod inspector;
mod ops;
use ops::*;
mod value;
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
type LocalValue<'a> = v8::Local<'a, v8::Value>;

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>),
}