temp
This commit is contained in:
82
Cargo.lock
generated
82
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"nix-js",
|
||||
"nix-js-macros"
|
||||
"nix-js"
|
||||
]
|
||||
|
||||
[profile.profiling]
|
||||
|
||||
@@ -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"] }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
3
nix-js/runtime-ts/package-lock.json
generated
3
nix-js/runtime-ts/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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
500
nix-js/src/runtime/value.rs
Normal 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>),
|
||||
}
|
||||
Reference in New Issue
Block a user