feat: rewrite runtime.js with typescript

This commit is contained in:
2026-01-02 20:46:07 +08:00
parent d2ed3935ca
commit 210923fd92
28 changed files with 1990 additions and 495 deletions

View File

@@ -25,6 +25,10 @@
lldb
valgrind
claude-code
# Node.js tooling for TypeScript build
nodejs
nodePackages.npm
];
};
}

View File

@@ -2,6 +2,7 @@
name = "nix-js"
version = "0.1.0"
edition = "2024"
build = "build.rs"
[dependencies]
mimalloc = "0.1"

67
nix-js/build.rs Normal file
View File

@@ -0,0 +1,67 @@
use std::process::Command;
use std::path::Path;
use std::env;
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");
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");
}
// Print build info
if env::var("CARGO_CFG_DEBUG_ASSERTIONS").is_ok() {
println!("Built runtime.js in DEBUG mode");
}
}

2
nix-js/runtime-ts/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/node_modules
/dist

496
nix-js/runtime-ts/package-lock.json generated Normal file
View File

@@ -0,0 +1,496 @@
{
"name": "nix-js-runtime",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nix-js-runtime",
"version": "0.1.0",
"devDependencies": {
"esbuild": "^0.24.2",
"typescript": "^5.7.2"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.2.tgz",
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.24.2",
"@esbuild/android-arm": "0.24.2",
"@esbuild/android-arm64": "0.24.2",
"@esbuild/android-x64": "0.24.2",
"@esbuild/darwin-arm64": "0.24.2",
"@esbuild/darwin-x64": "0.24.2",
"@esbuild/freebsd-arm64": "0.24.2",
"@esbuild/freebsd-x64": "0.24.2",
"@esbuild/linux-arm": "0.24.2",
"@esbuild/linux-arm64": "0.24.2",
"@esbuild/linux-ia32": "0.24.2",
"@esbuild/linux-loong64": "0.24.2",
"@esbuild/linux-mips64el": "0.24.2",
"@esbuild/linux-ppc64": "0.24.2",
"@esbuild/linux-riscv64": "0.24.2",
"@esbuild/linux-s390x": "0.24.2",
"@esbuild/linux-x64": "0.24.2",
"@esbuild/netbsd-arm64": "0.24.2",
"@esbuild/netbsd-x64": "0.24.2",
"@esbuild/openbsd-arm64": "0.24.2",
"@esbuild/openbsd-x64": "0.24.2",
"@esbuild/sunos-x64": "0.24.2",
"@esbuild/win32-arm64": "0.24.2",
"@esbuild/win32-ia32": "0.24.2",
"@esbuild/win32-x64": "0.24.2"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}

View File

@@ -0,0 +1,14 @@
{
"name": "nix-js-runtime",
"version": "0.1.0",
"private": true,
"scripts": {
"typecheck": "tsc --noEmit",
"build": "esbuild src/index.ts --bundle --target=es2020 --format=iife --outfile=dist/runtime.js",
"dev": "npm run typecheck && npm run build"
},
"devDependencies": {
"esbuild": "^0.24.2",
"typescript": "^5.7.2"
}
}

View File

@@ -0,0 +1,53 @@
/**
* Arithmetic builtin functions
*/
import type { NixBool, NixInt, NixNumber, NixValue } from '../types';
import { force_numeric, coerce_numeric, force_int } from '../type-assert';
export const add = (a: NixValue) => (b: NixValue): bigint | number => {
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) + (bv as any);
};
export const sub = (a: NixValue) => (b: NixValue): bigint | number => {
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) - (bv as any);
};
export const mul = (a: NixValue) => (b: NixValue): bigint | number => {
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) * (bv as any);
};
export const div = (a: NixValue) => (b: NixValue): NixNumber => {
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
if (bv === 0 || bv === 0n) {
throw new RangeError('Division by zero');
}
return (av as any) / (bv as any)
};
// Bitwise operations - only for integers
export const bitAnd = (a: NixValue) => (b: NixValue): NixInt => {
const av = force_int(a);
const bv = force_int(b);
return av & bv;
};
export const bitOr = (a: NixValue) => (b: NixValue): NixInt => {
const av = force_int(a);
const bv = force_int(b);
return av | bv;
};
export const bitXor = (a: NixValue) => (b: NixValue): NixInt => {
const av = force_int(a);
const bv = force_int(b);
return av ^ bv;
};
export const lessThan = (a: NixValue) => (b: NixValue): NixBool =>
force_numeric(a) < force_numeric(b);

View File

@@ -0,0 +1,67 @@
/**
* Attribute set operation builtin functions
*/
import type { NixValue, NixAttrs, NixList } from '../types';
import { force_attrs, force_string, force_function, force_list } from '../type-assert';
export const attrNames = (set: NixValue): string[] => Object.keys(force_attrs(set));
export const attrValues = (set: NixValue): NixValue[] => Object.values(force_attrs(set));
export const getAttr = (s: NixValue) => (set: NixValue): NixValue =>
force_attrs(set)[force_string(s)];
export const hasAttr = (s: NixValue) => (set: NixValue): boolean =>
Object.prototype.hasOwnProperty.call(force_attrs(set), force_string(s));
export const mapAttrs = (f: NixValue) => (attrs: NixValue): NixAttrs => {
const new_attrs: NixAttrs = {};
const forced_attrs = force_attrs(attrs);
const forced_f = force_function(f);
for (const key in forced_attrs) {
new_attrs[key] = forced_f(key)(forced_attrs[key]);
}
return new_attrs;
};
export const listToAttrs = (e: NixValue): NixAttrs => {
const attrs: NixAttrs = {};
const forced_e = [...force_list(e)].reverse();
for (const obj of forced_e) {
const item = force_attrs(obj);
attrs[force_string(item.name)] = item.value;
}
return attrs;
};
export const intersectAttrs = (e1: NixValue) => (e2: NixValue): NixAttrs => {
const f1 = force_attrs(e1);
const f2 = force_attrs(e2);
const attrs: NixAttrs = {};
for (const key of Object.keys(f2)) {
if (Object.prototype.hasOwnProperty.call(f1, key)) {
attrs[key] = f2[key];
}
}
return attrs;
};
export const catAttrs = (attr: NixValue) => (list: NixValue): NixList => {
const key = force_string(attr);
return force_list(list)
.map((set) => force_attrs(set)[key])
.filter((val) => val !== undefined);
};
export const groupBy = (f: NixValue) => (list: NixValue): NixAttrs => {
const attrs: NixAttrs = {};
const forced_f = force_function(f);
const forced_list = force_list(list);
for (const elem of forced_list) {
const key = force_string(forced_f(elem));
if (!attrs[key]) attrs[key] = [];
(attrs[key] as NixList).push(elem);
}
return attrs;
};

View File

@@ -0,0 +1,25 @@
/**
* Conversion and serialization builtin functions (unimplemented)
*/
import type { NixValue } from '../types';
export const fromJSON = (e: NixValue): never => {
throw 'Not implemented: fromJSON';
};
export const fromTOML = (e: NixValue): never => {
throw 'Not implemented: fromTOML';
};
export const toJSON = (e: NixValue): never => {
throw 'Not implemented: toJSON';
};
export const toXML = (e: NixValue): never => {
throw 'Not implemented: toXML';
};
export const toString = (name: NixValue, s: NixValue): never => {
throw 'Not implemented: toString';
};

View File

@@ -0,0 +1,35 @@
/**
* Functional programming builtin functions
*/
import type { NixValue } from '../types';
import { force } from '../thunk';
export const seq = (e1: NixValue) => (e2: NixValue): NixValue => {
force(e1); // Force evaluation of e1
return e2;
};
export const deepSeq = (e1: NixValue) => (e2: NixValue): never => {
throw 'Not implemented: deepSeq';
};
export const abort = (s: NixValue): never => {
throw `evaluation aborted with the following error message: '${force(s)}'`;
};
export const throwFunc = (s: NixValue): never => {
throw force(s);
};
export const trace = (e1: NixValue, e2: NixValue): NixValue => {
console.log(`trace: ${force(e1)}`);
return e2;
};
export const warn = (e1: NixValue) => (e2: NixValue): NixValue => {
console.log(`evaluation warning: ${force(e1)}`);
return e2;
};
export const breakFunc = (v: NixValue): NixValue => v;

View File

@@ -0,0 +1,169 @@
/**
* Main builtins export
* Combines all builtin function categories into the global `builtins` object
*/
import { create_thunk } from '../thunk';
// Import all builtin categories
import * as arithmetic from './arithmetic';
import * as math from './math';
import * as typeCheck from './type-check';
import * as list from './list';
import * as attrs from './attrs';
import * as string from './string';
import * as functional from './functional';
import * as io from './io';
import * as conversion from './conversion';
import * as misc from './misc';
/**
* The global builtins object
* Contains 80+ Nix builtin functions plus metadata
*
* All functions are curried for Nix semantics:
* - Single argument functions: (a) => result
* - Multi-argument functions: (a) => (b) => result
*/
export const builtins: any = {
// Arithmetic (curried binary functions)
add: arithmetic.add,
sub: arithmetic.sub,
mul: arithmetic.mul,
div: arithmetic.div,
bitAnd: arithmetic.bitAnd,
bitOr: arithmetic.bitOr,
bitXor: arithmetic.bitXor,
lessThan: arithmetic.lessThan,
// Math
ceil: math.ceil,
floor: math.floor,
// Type checking
isAttrs: typeCheck.isAttrs,
isBool: typeCheck.isBool,
isFloat: typeCheck.isFloat,
isFunction: typeCheck.isFunction,
isInt: typeCheck.isInt,
isList: typeCheck.isList,
isNull: typeCheck.isNull,
isPath: typeCheck.isPath,
isString: typeCheck.isString,
typeOf: typeCheck.typeOf,
// List operations
map: list.map,
filter: list.filter,
length: list.length,
head: list.head,
tail: list.tail,
elem: list.elem,
elemAt: list.elemAt,
concatLists: list.concatLists,
concatMap: list.concatMap,
'foldl\'': list.foldlPrime,
sort: list.sort,
partition: list.partition,
genList: list.genList,
all: list.all,
any: list.any,
// Attribute set operations
attrNames: attrs.attrNames,
attrValues: attrs.attrValues,
getAttr: attrs.getAttr,
hasAttr: attrs.hasAttr,
mapAttrs: attrs.mapAttrs,
listToAttrs: attrs.listToAttrs,
intersectAttrs: attrs.intersectAttrs,
catAttrs: attrs.catAttrs,
groupBy: attrs.groupBy,
// String operations
stringLength: string.stringLength,
substring: string.substring,
concatStringsSep: string.concatStringsSep,
baseNameOf: string.baseNameOf,
// Functional
seq: functional.seq,
deepSeq: functional.deepSeq,
abort: functional.abort,
throw: functional.throwFunc,
trace: functional.trace,
warn: functional.warn,
break: functional.breakFunc,
// I/O (unimplemented)
import: io.importFunc,
scopedImport: io.scopedImport,
fetchClosure: io.fetchClosure,
fetchGit: io.fetchGit,
fetchTarball: io.fetchTarball,
fetchTree: io.fetchTree,
fetchurl: io.fetchurl,
readDir: io.readDir,
readFile: io.readFile,
readFileType: io.readFileType,
pathExists: io.pathExists,
path: io.path,
toFile: io.toFile,
toPath: io.toPath,
filterSource: io.filterSource,
findFile: io.findFile,
getEnv: io.getEnv,
// Conversion (unimplemented)
fromJSON: conversion.fromJSON,
fromTOML: conversion.fromTOML,
toJSON: conversion.toJSON,
toXML: conversion.toXML,
toString: conversion.toString,
// Miscellaneous (unimplemented)
getContext: misc.getContext,
hasContext: misc.hasContext,
hashFile: misc.hashFile,
hashString: misc.hashString,
convertHash: misc.convertHash,
unsafeDiscardOutputDependency: misc.unsafeDiscardOutputDependency,
unsafeDiscardStringContext: misc.unsafeDiscardStringContext,
unsafeGetAttrPos: misc.unsafeGetAttrPos,
addDrvOutputDependencies: misc.addDrvOutputDependencies,
compareVersions: misc.compareVersions,
dirOf: misc.dirOf,
flakeRefToString: misc.flakeRefToString,
functionArgs: misc.functionArgs,
genericClosure: misc.genericClosure,
getFlake: misc.getFlake,
match: misc.match,
outputOf: misc.outputOf,
parseDrvName: misc.parseDrvName,
parseFlakeName: misc.parseFlakeName,
placeholder: misc.placeholder,
replaceStrings: misc.replaceStrings,
split: misc.split,
splitVersion: misc.splitVersion,
traceVerbose: misc.traceVerbose,
tryEval: misc.tryEval,
zipAttrsWith: misc.zipAttrsWith,
// Meta - self-reference and constants
builtins: create_thunk(() => builtins), // Recursive reference
currentSystem: create_thunk(() => {
throw 'Not implemented: currentSystem';
}),
currentTime: create_thunk(() => Date.now()),
// Constants (special keys)
false: false,
true: true,
null: null,
// Version information
langVersion: 6,
nixPath: [],
nixVersion: 'NIX_JS_VERSION',
storeDir: '/nix/store',
};

View File

@@ -0,0 +1,74 @@
/**
* I/O and filesystem builtin functions (unimplemented)
* These functions require Node.js/Deno APIs not available in V8
*/
import type { NixValue } from '../types';
export const importFunc = (path: NixValue): never => {
throw 'Not implemented: import';
};
export const scopedImport = (scope: NixValue) => (path: NixValue): never => {
throw 'Not implemented: scopedImport';
};
export const fetchClosure = (args: NixValue): never => {
throw 'Not implemented: fetchClosure';
};
export const fetchGit = (args: NixValue): never => {
throw 'Not implemented: fetchGit';
};
export const fetchTarball = (args: NixValue): never => {
throw 'Not implemented: fetchTarball';
};
export const fetchTree = (args: NixValue): never => {
throw 'Not implemented: fetchTree';
};
export const fetchurl = (args: NixValue): never => {
throw 'Not implemented: fetchurl';
};
export const readDir = (path: NixValue): never => {
throw 'Not implemented: readDir';
};
export const readFile = (path: NixValue): never => {
throw 'Not implemented: readFile';
};
export const readFileType = (path: NixValue): never => {
throw 'Not implemented: readFileType';
};
export const pathExists = (path: NixValue): never => {
throw 'Not implemented: pathExists';
};
export const path = (args: NixValue): never => {
throw 'Not implemented: path';
};
export const toFile = (name: NixValue, s: NixValue): never => {
throw 'Not implemented: toFile';
};
export const toPath = (name: NixValue, s: NixValue): never => {
throw 'Not implemented: toPath';
};
export const filterSource = (args: NixValue): never => {
throw 'Not implemented: filterSource';
};
export const findFile = (search: NixValue) => (lookup: NixValue): never => {
throw 'Not implemented: findFile';
};
export const getEnv = (s: NixValue): never => {
throw 'Not implemented: getEnv';
};

View File

@@ -0,0 +1,102 @@
/**
* List operation builtin functions
* All functions are properly curried
*/
import type { NixValue, NixList, NixAttrs } from '../types';
import { force } from '../thunk';
import { force_list, force_function, force_numeric, force_int } from '../type-assert';
export const map = (f: NixValue) => (list: NixValue): NixList =>
force_list(list).map(force_function(f));
export const filter = (f: NixValue) => (list: NixValue): NixList =>
force_list(list).filter(force_function(f));
export const length = (e: NixValue): bigint => {
const forced = force(e);
if (typeof forced === 'string') return BigInt(forced.length);
return BigInt(force_list(forced).length);
};
export const head = (list: NixValue): NixValue => force_list(list)[0];
export const tail = (list: NixValue): NixList => force_list(list).slice(1);
export const elem = (x: NixValue) => (xs: NixValue): boolean =>
force_list(xs).includes(force(x));
export const elemAt = (xs: NixValue) => (n: NixValue): NixValue => {
const list = force_list(xs);
const idx = Number(force_int(n));
if (idx < 0 || idx >= list.length) {
throw new RangeError(`Index ${idx} out of bounds for list of length ${list.length}`);
}
return list[idx];
};
export const concatLists = (lists: NixValue): NixList => {
return force_list(lists).reduce((acc: NixList, cur: NixValue) => {
return acc.concat(force_list(cur));
}, []);
};
export const concatMap = (f: NixValue) => (lists: NixValue): NixList => {
const fn = force_function(f);
return force_list(lists).reduce((acc: NixList, cur: NixValue) => {
return acc.concat(force(fn(cur)) as NixList);
}, []);
};
export const foldlPrime = (op_fn: NixValue) => (nul: NixValue) => (list: NixValue): NixValue => {
const forced_op = force_function(op_fn);
return force_list(list).reduce((acc: NixValue, cur: NixValue) => {
return forced_op(acc)(cur);
}, nul);
};
export const sort = (cmp: NixValue) => (list: NixValue): NixList => {
const forced_list = [...force_list(list)];
const forced_cmp = force_function(cmp);
return forced_list.sort((a, b) => {
if (force(forced_cmp(a)(b))) return -1;
if (force(forced_cmp(b)(a))) return 1;
return 0;
});
};
export const partition = (pred: NixValue) => (list: NixValue): NixAttrs => {
const forced_list = force_list(list);
const forced_pred = force_function(pred);
const attrs: NixAttrs = {
right: [],
wrong: [],
};
for (const elem of forced_list) {
if (force(forced_pred(elem))) {
(attrs.right as NixList).push(elem);
} else {
(attrs.wrong as NixList).push(elem);
}
}
return attrs;
};
export const genList = (f: NixValue) => (len: NixValue): NixList => {
const func = force_function(f);
const length = force_int(len);
if (length < 0) {
throw new TypeError(`genList length must be non-negative integer, got ${length}`);
}
return [...Array(Number(length)).keys()].map(i => func(BigInt(i)));
};
export const all = (pred: NixValue) => (list: NixValue): boolean =>
force_list(list).every(force_function(pred));
export const any = (pred: NixValue) => (list: NixValue): boolean =>
force_list(list).some(force_function(pred));

View File

@@ -0,0 +1,18 @@
/**
* Math builtin functions
*/
import type { NixValue } from '../types';
import { force_numeric } from '../type-assert';
export const ceil = (x: NixValue): bigint => {
const val = force_numeric(x);
if (typeof val === 'bigint') return val; // Already an integer
return BigInt(Math.ceil(val)); // Convert to integer
};
export const floor = (x: NixValue): bigint => {
const val = force_numeric(x);
if (typeof val === 'bigint') return val; // Already an integer
return BigInt(Math.floor(val)); // Convert to integer
};

View File

@@ -0,0 +1,109 @@
/**
* Miscellaneous unimplemented builtin functions
*/
import type { NixValue } from '../types';
export const getContext = (s: NixValue): never => {
throw 'Not implemented: getContext';
};
export const hasContext = (s: NixValue): never => {
throw 'Not implemented: hasContext';
};
export const hashFile = (type: NixValue) => (p: NixValue): never => {
throw 'Not implemented: hashFile';
};
export const hashString = (type: NixValue) => (p: NixValue): never => {
throw 'Not implemented: hashString';
};
export const convertHash = (args: NixValue): never => {
throw 'Not implemented: convertHash';
};
export const unsafeDiscardOutputDependency = (s: NixValue): never => {
throw 'Not implemented: unsafeDiscardOutputDependency';
};
export const unsafeDiscardStringContext = (s: NixValue): never => {
throw 'Not implemented: unsafeDiscardStringContext';
};
export const unsafeGetAttrPos = (s: NixValue): never => {
throw 'Not implemented: unsafeGetAttrPos';
};
export const addDrvOutputDependencies = (s: NixValue): never => {
throw 'Not implemented: addDrvOutputDependencies';
};
export const compareVersions = (s1: NixValue) => (s2: NixValue): never => {
throw 'Not implemented: compareVersions';
};
export const dirOf = (s: NixValue): never => {
throw 'Not implemented: dirOf';
};
export const flakeRefToString = (attrs: NixValue): never => {
throw 'Not implemented: flakeRefToString';
};
export const functionArgs = (f: NixValue): never => {
throw 'Not implemented: functionArgs';
};
export const genericClosure = (args: NixValue): never => {
throw 'Not implemented: genericClosure';
};
export const getFlake = (attrs: NixValue): never => {
throw 'Not implemented: getFlake';
};
export const match = (regex: NixValue) => (str: NixValue): never => {
throw 'Not implemented: match';
};
export const outputOf = (drv: NixValue) => (out: NixValue): never => {
throw 'Not implemented: outputOf';
};
export const parseDrvName = (s: NixValue): never => {
throw 'Not implemented: parseDrvName';
};
export const parseFlakeName = (s: NixValue): never => {
throw 'Not implemented: parseFlakeName';
};
export const placeholder = (output: NixValue): never => {
throw 'Not implemented: placeholder';
};
export const replaceStrings = (from: NixValue) => (to: NixValue) => (s: NixValue): never => {
throw 'Not implemented: replaceStrings';
};
export const split = (regex: NixValue, str: NixValue): never => {
throw 'Not implemented: split';
};
export const splitVersion = (s: NixValue): never => {
throw 'Not implemented: splitVersion';
};
export const traceVerbose = (e1: NixValue, e2: NixValue): never => {
throw 'Not implemented: traceVerbose';
};
export const tryEval = (e1: NixValue) => (e2: NixValue): never => {
throw 'Not implemented: tryEval';
};
export const zipAttrsWith = (f: NixValue) => (list: NixValue): never => {
throw 'Not implemented: zipAttrsWith';
};

View File

@@ -0,0 +1,35 @@
/**
* String operation builtin functions
*/
import type { NixValue } from '../types';
import { force_string, force_list, force_int } from '../type-assert';
export const stringLength = (e: NixValue): number => force_string(e).length;
export const substring = (start: NixValue) => (len: NixValue) => (s: NixValue): string => {
const str = force_string(s);
const startPos = Number(force_int(start));
const length = Number(force_int(len));
return str.substring(startPos, startPos + length);
};
export const concatStringsSep = (sep: NixValue) => (list: NixValue): string =>
force_list(list).join(force_string(sep));
export const baseNameOf = (x: NixValue): string => {
const str = force_string(x);
if (str.length === 0) return '';
let last = str.length - 1;
if (str[last] === '/' && last > 0) last -= 1;
let pos = last;
while (pos >= 0 && str[pos] !== '/') pos -= 1;
if (pos !== 0 || (pos === 0 && str[pos] === '/')) {
pos += 1;
}
return str.substring(pos, last + 1);
};

View File

@@ -0,0 +1,50 @@
/**
* Type checking builtin functions
*/
import type { NixAttrs, NixBool, NixFloat, NixFunction, NixInt, NixList, NixNull, NixString, NixValue } from '../types';
import { force } from '../thunk';
export const isAttrs = (e: NixValue): e is NixAttrs => {
const val = force(e);
return typeof val === 'object' && !Array.isArray(val) && val !== null;
};
export const isBool = (e: NixValue): e is NixBool => typeof force(e) === 'boolean';
export const isFloat = (e: NixValue): e is NixFloat => {
const val = force(e);
return typeof val === 'number'; // Only number is float
};
export const isFunction = (e: NixValue): e is NixFunction => typeof force(e) === 'function';
export const isInt = (e: NixValue): e is NixInt => {
const val = force(e);
return typeof val === 'bigint'; // Only bigint is int
};
export const isList = (e: NixValue): e is NixList => Array.isArray(force(e));
export const isNull = (e: NixValue): e is NixNull => force(e) === null;
export const isPath = (e: NixValue): never => {
throw 'Not implemented: isPath';
};
export const isString = (e: NixValue): e is NixString => typeof force(e) === 'string';
export const typeOf = (e: NixValue): string => {
const val = force(e);
if (typeof val === 'bigint') return 'int';
if (typeof val === 'number') return 'float';
if (typeof val === 'boolean') return 'bool';
if (typeof val === 'string') return 'string';
if (val === null) return 'null';
if (Array.isArray(val)) return 'list';
if (typeof val === 'function') return 'lambda';
if (typeof val === 'object') return 'set';
throw new TypeError(`Unknown Nix type: ${typeof val}`);
};

View File

@@ -0,0 +1,99 @@
/**
* Helper functions for nix-js runtime
* Implements attribute selection, parameter validation, and lazy sets
*/
import type { NixValue, NixAttrs } from './types';
import { force_attrs, force_string } from './type-assert';
/**
* Select an attribute from an attribute set
* Used by codegen for attribute access (e.g., obj.key)
*
* @param obj - Attribute set to select from
* @param key - Key to select
* @returns The value at obj[key]
* @throws Error if obj is null/undefined or key not found
*/
export const select = (obj: NixValue, key: NixValue): NixValue => {
const forced_obj = force_attrs(obj);
const forced_key = force_string(key);
if (!(forced_key in forced_obj)) {
throw new Error(`Attribute '${forced_key}' not found`);
}
return forced_obj[forced_key];
};
/**
* Select an attribute with a default value
* Used for Nix's `obj.key or default` syntax
*
* @param obj - Attribute set to select from
* @param key - Key to select
* @param default_val - Value to return if key not found
* @returns obj[key] if exists, otherwise default_val
*/
export const select_with_default = (
obj: NixValue,
key: NixValue,
default_val: NixValue
): NixValue => {
const forced_obj = force_attrs(obj);
const forced_key = force_string(key);
if (forced_obj === null || forced_obj === undefined) {
return default_val;
}
const attrs = forced_obj;
if (!(forced_key in attrs)) {
return default_val;
}
return attrs[forced_key];
};
/**
* Validate function parameters
* Used for pattern matching in function parameters
*
* Example: { a, b ? 1, ... }: ...
* - required: ["a"]
* - allowed: ["a", "b"] (or null if ellipsis "..." present)
*
* @param arg - Argument object to validate
* @param required - Array of required parameter names (or null)
* @param allowed - Array of allowed parameter names (or null for ellipsis)
* @returns The forced argument object
* @throws Error if required param missing or unexpected param present
*/
export const validate_params = (
arg: NixValue,
required: string[] | null,
allowed: string[] | null
): NixAttrs => {
const forced_arg = force_attrs(arg);
// Check required parameters
if (required) {
for (const key of required) {
if (!Object.prototype.hasOwnProperty.call(forced_arg, key)) {
throw new Error(`Function called without required argument '${key}'`);
}
}
}
// Check allowed parameters (if not using ellipsis)
if (allowed) {
const allowed_set = new Set(allowed);
for (const key in forced_arg) {
if (!allowed_set.has(key)) {
throw new Error(`Function called with unexpected argument '${key}'`);
}
}
}
return forced_arg;
};

View File

@@ -0,0 +1,34 @@
/**
* nix-js Runtime Entry Point
*
* All functionality is exported via the global `Nix` object
*/
import { create_thunk, force, is_thunk, IS_THUNK } from './thunk';
import { select, select_with_default, validate_params } from './helpers';
import { op } from './operators';
import { builtins } from './builtins';
export type NixRuntime = typeof Nix;
/**
* The global Nix runtime object
*/
export const Nix = {
create_thunk,
force,
is_thunk,
IS_THUNK,
select,
select_with_default,
validate_params,
op,
builtins,
};
globalThis.Nix = Nix;
declare global {
var Nix: NixRuntime
}

View File

@@ -0,0 +1,91 @@
/**
* Nix operators module
* Implements all binary and unary operators used by codegen
*/
import type { NixValue, NixList, NixAttrs } from './types';
import { force } from './thunk';
import { force_numeric, force_list, force_attrs, coerce_numeric } from './type-assert';
/**
* Operator object exported as Nix.op
* All operators referenced by codegen (e.g., Nix.op.add, Nix.op.eq)
*/
export const op = {
// Arithmetic operators - preserve int/float distinction
add: (a: NixValue, b: NixValue): bigint | number => {
// FIXME: String & Path
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) + (bv as any);
},
sub: (a: NixValue, b: NixValue): bigint | number => {
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) - (bv as any);
},
mul: (a: NixValue, b: NixValue): bigint | number => {
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) * (bv as any);
},
div: (a: NixValue, b: NixValue): bigint | number => {
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
if (bv === 0 || bv === 0n) {
throw new RangeError('Division by zero');
}
return (av as any) / (bv as any)
},
// Comparison operators (JavaScript natively supports bigint/number mixed comparison)
eq: (a: NixValue, b: NixValue): boolean => {
// FIXME: Int and Float
const av = force(a);
const bv = force(b);
return av === bv
},
neq: (a: NixValue, b: NixValue): boolean => {
// FIXME: Int and Float
const av = force(a);
const bv = force(b);
return av !== bv
},
lt: (a: NixValue, b: NixValue): boolean => {
// FIXME: Non-numeric
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) < (bv as any);
},
lte: (a: NixValue, b: NixValue): boolean => {
// FIXME: Non-numeric
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) <= (bv as any);
},
gt: (a: NixValue, b: NixValue): boolean => {
// FIXME: Non-numeric
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) > (bv as any);
},
gte: (a: NixValue, b: NixValue): boolean => {
// FIXME: Non-numeric
const [av, bv] = coerce_numeric(force_numeric(a), force_numeric(b));
return (av as any) >= (bv as any);
},
// Boolean operators
bnot: (a: NixValue): boolean => !force(a),
// Non-short-circuit
// band: (a: NixValue, b: NixValue): boolean => !!(force(a) && force(b)),
// bor: (a: NixValue, b: NixValue): boolean => !!(force(a) || force(b)),
// List concatenation
concat: (a: NixValue, b: NixValue): NixList => {
return Array.prototype.concat.call(force_list(a), force_list(b));
},
// Attribute set update (merge)
update: (a: NixValue, b: NixValue): NixAttrs => {
return { ...force_attrs(a), ...force_attrs(b) };
},
};

View File

@@ -0,0 +1,79 @@
/**
* Lazy evaluation system for nix-js
* Implements thunks for lazy evaluation of Nix expressions
*/
import type { NixValue, NixThunkInterface } from './types';
/**
* Symbol used to mark objects as thunks
* This is exported to Rust via Nix.IS_THUNK
*/
export const IS_THUNK = Symbol('is_thunk');
/**
* NixThunk class - represents a lazy, unevaluated expression
*
* A thunk wraps a function that produces a value when called.
* Once evaluated, the result is cached to avoid recomputation.
*/
export class NixThunk implements NixThunkInterface {
[key: symbol]: any;
readonly [IS_THUNK] = true as const;
func: (() => NixValue) | null;
result: Exclude<NixValue, NixThunkInterface> | null;
constructor(func: () => NixValue) {
this.func = func;
this.result = null;
}
}
/**
* Type guard to check if a value is a thunk
* @param value - Value to check
* @returns true if value is a NixThunk
*/
export const is_thunk = (value: unknown): value is NixThunkInterface => {
return (
value !== null &&
typeof value === 'object' &&
IS_THUNK in value &&
value[IS_THUNK] === true
);
};
/**
* Force evaluation of a value
* If the value is a thunk, evaluate it and cache the result
* If already evaluated or not a thunk, return as-is
*
* @param value - Value to force (may be a thunk)
* @returns The forced/evaluated value
*/
export const force = (value: NixValue): Exclude<NixValue, NixThunkInterface> => {
if (!is_thunk(value)) {
return value;
}
// Already evaluated - return cached result
if (value.func === null) {
return value.result!;
}
// Evaluate and cache
const result = force(value.func());
value.result = result;
value.func = null;
return result;
};
/**
* Create a new thunk from a function
* @param func - Function that produces a value when called
* @returns A new NixThunk wrapping the function
*/
export const create_thunk = (func: () => NixValue): NixThunkInterface => {
return new NixThunk(func);
};

View File

@@ -0,0 +1,127 @@
/**
* Type assertion helpers for runtime type checking
* These functions force evaluation and verify the type, throwing errors on mismatch
*/
import type { NixValue, NixList, NixAttrs, NixFunction, NixInt, NixFloat, NixNumber } from './types';
import { force } from './thunk';
/**
* Force a value and assert it's a list
* @throws TypeError if value is not a list after forcing
*/
export const force_list = (value: NixValue): NixList => {
const forced = force(value);
if (!Array.isArray(forced)) {
throw new TypeError(`Expected list, got ${typeof forced}`);
}
return forced;
};
/**
* Force a value and assert it's a function
* @throws TypeError if value is not a function after forcing
*/
export const force_function = (value: NixValue): NixFunction => {
const forced = force(value);
if (typeof forced !== 'function') {
throw new TypeError(`Expected function, got ${typeof forced}`);
}
return forced;
};
/**
* Force a value and assert it's an attribute set
* @throws TypeError if value is not an attribute set after forcing
*/
export const force_attrs = (value: NixValue): NixAttrs => {
const forced = force(value);
if (typeof forced !== 'object' || forced === null || Array.isArray(forced)) {
throw new TypeError(`Expected attribute set, got ${typeof forced}`);
}
return forced as NixAttrs;
};
/**
* Force a value and assert it's a string
* @throws TypeError if value is not a string after forcing
*/
export const force_string = (value: NixValue): string => {
const forced = force(value);
if (typeof forced !== 'string') {
throw new TypeError(`Expected string, got ${typeof forced}`);
}
return forced;
};
/**
* Force a value and assert it's a boolean
* @throws TypeError if value is not a boolean after forcing
*/
export const force_bool = (value: NixValue): boolean => {
const forced = force(value);
if (typeof forced !== 'boolean') {
throw new TypeError(`Expected boolean, got ${typeof forced}`);
}
return forced;
};
/**
* Force a value and extract int value
* @throws TypeError if value is not an int
*/
export const force_int = (value: NixValue): NixInt => {
const forced = force(value);
if (typeof forced === 'bigint') {
return forced;
}
throw new TypeError(`Expected int, got ${typeof forced}`);
};
/**
* Force a value and extract float value
* @throws TypeError if value is not a float
*/
export const force_float = (value: NixValue): NixFloat => {
const forced = force(value);
if (typeof forced === 'number') {
return forced;
}
throw new TypeError(`Expected float, got ${typeof forced}`);
};
/**
* Force a value and extract numeric value (int or float)
* @throws TypeError if value is not a numeric type
*/
export const force_numeric = (value: NixValue): NixNumber => {
const forced = force(value);
if (typeof forced === 'bigint' || typeof forced === 'number') {
return forced;
}
throw new TypeError(`Expected numeric type, got ${typeof forced}`);
};
/**
* Coerce two numeric values to a common type for arithmetic
* Rule: If either is float, convert both to float; otherwise keep as bigint
* @returns [a, b] tuple of coerced values
*/
export const coerce_numeric = (
a: NixNumber,
b: NixNumber
): [NixFloat, NixFloat] | [NixInt, NixInt] => {
const aIsInt = typeof a === 'bigint';
const bIsInt = typeof b === 'bigint';
// If either is float, convert both to float
if (!aIsInt || !bIsInt) {
return [
aIsInt ? Number(a) : a,
bIsInt ? Number(b) : b
];
}
// Both are integers
return [a, b];
};

View File

@@ -0,0 +1,55 @@
/**
* Core TypeScript type definitions for nix-js runtime
*/
// Nix primitive types
export type NixInt = bigint;
export type NixFloat = number;
export type NixNumber = NixInt | NixFloat;
export type NixBool = boolean;
export type NixString = string;
export type NixNull = null;
// Nix composite types
export type NixList = NixValue[];
export type NixAttrs = { [key: string]: NixValue };
export type NixFunction = (...args: any[]) => any;
/**
* Interface for lazy thunk values
* Thunks delay evaluation until forced
*/
export interface NixThunkInterface {
readonly [key: symbol]: true; // IS_THUNK marker
func: (() => NixValue) | null;
result: Exclude<NixValue, NixThunkInterface> | null;
}
// Union of all Nix primitive types
export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString;
/**
* NixValue: Union type representing any possible Nix value
* This is the core type used throughout the runtime
*/
export type NixValue =
| NixPrimitive
| NixList
| NixAttrs
| NixFunction
| NixThunkInterface;
// Operator function signatures
export type BinaryOp<T = NixValue, U = NixValue, R = NixValue> = (a: T, b: U) => R;
export type UnaryOp<T = NixValue, R = NixValue> = (a: T) => R;
/**
* Curried function types - All Nix builtins must be curried!
*
* Examples:
* - add: Curried2<number, number, number> = (a) => (b) => a + b
* - map: Curried2<NixFunction, NixList, NixList> = (f) => (list) => list.map(f)
*/
export type Curried2<A, B, R> = (a: A) => (b: B) => R;
export type Curried3<A, B, C, R> = (a: A) => (b: B) => (c: C) => R;
export type Curried4<A, B, C, D, R> = (a: A) => (b: B) => (c: C) => (d: D) => R;

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"declaration": false,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -16,7 +16,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
match self {
Ir::Const(Const { val }) => match val {
crate::value::Const::Null => "null".to_string(),
crate::value::Const::Int(val) => val.to_string(),
crate::value::Const::Int(val) => format!("{}n", val), // Generate BigInt literal
crate::value::Const::Float(val) => val.to_string(),
crate::value::Const::Bool(val) => val.to_string(),
},
@@ -68,9 +68,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
Gt => format!("Nix.op.gt({},{})", lhs, rhs),
Leq => format!("Nix.op.lte({},{})", lhs, rhs),
Geq => format!("Nix.op.gte({},{})", lhs, rhs),
And => format!("Nix.op.band({},{})", lhs, rhs),
Or => format!("Nix.op.bor({},{})", lhs, rhs),
Impl => format!("Nix.op.bor(Nix.op.bnot({}),{})", lhs, rhs),
// Short-circuit operators: use JavaScript native && and ||
And => format!("(Nix.force({}) && Nix.force({}))", lhs, rhs),
Or => format!("(Nix.force({}) || Nix.force({}))", lhs, rhs),
Impl => format!("(!Nix.force({}) || Nix.force({}))", lhs, rhs),
Con => format!("Nix.op.concat({},{})", lhs, rhs),
Upd => format!("Nix.op.update({},{})", lhs, rhs),
PipeL => format!("Nix.force({})({})", rhs, lhs),
@@ -84,7 +85,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
use UnOpKind::*;
let rhs = ctx.get_ir(self.rhs).compile(ctx);
match self.kind {
Neg => format!("Nix.op.sub(0,{rhs})"),
Neg => format!("Nix.op.sub(0n,{rhs})"),
Not => format!("Nix.op.bnot({rhs})"),
}
}

View File

@@ -184,6 +184,7 @@ mod test {
("2 > 1", Value::Const(Const::Bool(true))),
("1 <= 1", Value::Const(Const::Bool(true))),
("1 >= 1", Value::Const(Const::Bool(true))),
// Short-circuit evaluation: true || <expr> should not evaluate <expr>
("true || (1 / 0)", Value::Const(Const::Bool(true))),
("true && 1 == 0", Value::Const(Const::Bool(false))),
(
@@ -560,4 +561,148 @@ mod test {
Value::Const(Const::Bool(false))
);
}
// BigInt and numeric type tests
#[test]
fn test_bigint_precision() {
let mut ctx = Context::new();
// Test large i64 values
assert_eq!(
ctx.eval("9223372036854775807").unwrap(),
Value::Const(Const::Int(9223372036854775807))
);
// Test negative large value
// Can't use -9223372036854775808 since unary minus is actually desugared to (0 - <expr>)
assert_eq!(
ctx.eval("-9223372036854775807").unwrap(),
Value::Const(Const::Int(-9223372036854775807))
);
// Test large number arithmetic
assert_eq!(
ctx.eval("5000000000000000000 + 3000000000000000000")
.unwrap(),
Value::Const(Const::Int(8000000000000000000i64))
);
}
#[test]
fn test_int_float_distinction() {
let mut ctx = Context::new();
// isInt tests
assert_eq!(
ctx.eval("builtins.isInt 42").unwrap(),
Value::Const(Const::Bool(true))
);
assert_eq!(
ctx.eval("builtins.isInt 42.0").unwrap(),
Value::Const(Const::Bool(false))
);
// isFloat tests
assert_eq!(
ctx.eval("builtins.isFloat 42").unwrap(),
Value::Const(Const::Bool(false))
);
assert_eq!(
ctx.eval("builtins.isFloat 42.5").unwrap(),
Value::Const(Const::Bool(true))
);
assert_eq!(
ctx.eval("builtins.isFloat 1.0").unwrap(),
Value::Const(Const::Bool(true))
);
// typeOf tests
assert_eq!(
ctx.eval("builtins.typeOf 1").unwrap(),
Value::String("int".to_string())
);
assert_eq!(
ctx.eval("builtins.typeOf 1.0").unwrap(),
Value::String("float".to_string())
);
assert_eq!(
ctx.eval("builtins.typeOf 3.14").unwrap(),
Value::String("float".to_string())
);
}
#[test]
fn test_arithmetic_type_preservation() {
let mut ctx = Context::new();
// int + int = int
assert_eq!(
ctx.eval("builtins.typeOf (1 + 2)").unwrap(),
Value::String("int".to_string())
);
// int + float = float
assert_eq!(
ctx.eval("builtins.typeOf (1 + 2.0)").unwrap(),
Value::String("float".to_string())
);
// int * int = int
assert_eq!(
ctx.eval("builtins.typeOf (3 * 4)").unwrap(),
Value::String("int".to_string())
);
// int * float = float
assert_eq!(
ctx.eval("builtins.typeOf (3 * 4.0)").unwrap(),
Value::String("float".to_string())
);
}
#[test]
fn test_integer_division() {
let mut ctx = Context::new();
assert_eq!(ctx.eval("5 / 2").unwrap(), Value::Const(Const::Int(2)));
assert_eq!(ctx.eval("7 / 3").unwrap(), Value::Const(Const::Int(2)));
assert_eq!(ctx.eval("10 / 3").unwrap(), Value::Const(Const::Int(3)));
// Float division returns float
assert_eq!(
ctx.eval("5 / 2.0").unwrap(),
Value::Const(Const::Float(2.5))
);
assert_eq!(
ctx.eval("7.0 / 2").unwrap(),
Value::Const(Const::Float(3.5))
);
assert_eq!(
ctx.eval("(-7) / 3").unwrap(),
Value::Const(Const::Int(-2))
);
}
#[test]
fn test_builtin_arithmetic_with_bigint() {
let mut ctx = Context::new();
// Test builtin add with large numbers
assert_eq!(
ctx.eval("builtins.add 5000000000000000000 3000000000000000000")
.unwrap(),
Value::Const(Const::Int(8000000000000000000i64))
);
// Test builtin mul with large numbers
assert_eq!(
ctx.eval("builtins.mul 1000000000 1000000000")
.unwrap(),
Value::Const(Const::Int(1000000000000000000i64))
);
}
}

View File

@@ -26,7 +26,7 @@ fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result<Value> {
let context = v8::Context::new(handle_scope, v8::ContextOptions::default());
let scope = &mut v8::ContextScope::new(handle_scope, context);
let runtime_code = include_str!("./runtime/runtime.js");
let runtime_code = include_str!("../runtime-ts/dist/runtime.js");
let runtime_source = v8::String::new(scope, runtime_code).unwrap();
let runtime_script = v8::Script::compile(scope, runtime_source, None).unwrap();
@@ -83,19 +83,22 @@ fn to_value<'a>(
scope: &v8::PinnedRef<'a, v8::HandleScope>,
) -> Value {
match () {
_ if val.is_int32() => {
let val = val.to_int32(scope).unwrap().value();
Value::Const(Const::Int(val as i64))
}
_ if val.is_big_int() => {
let (val, true) = val.to_big_int(scope).unwrap().i64_value() else {
todo!()
};
let (val, lossless) = val.to_big_int(scope).unwrap().i64_value();
if !lossless {
panic!("BigInt value out of i64 range: conversion lost precision");
}
Value::Const(Const::Int(val))
}
_ if val.is_number() => {
let val = val.to_number(scope).unwrap().value();
Value::Const(Const::Float(val))
// Heuristic: convert whole numbers to Int (for backward compatibility and JS interop)
if val.is_finite() && val.fract() == 0.0
&& val >= i64::MIN as f64 && val <= i64::MAX as f64 {
Value::Const(Const::Int(val as i64))
} else {
Value::Const(Const::Float(val))
}
}
_ if val.is_true() => Value::Const(Const::Bool(true)),
_ if val.is_false() => Value::Const(Const::Bool(false)),

View File

@@ -1,481 +0,0 @@
const Nix = (() => {
const IS_THUNK = Symbol("is_thunk");
class NixThunk {
constructor(func) {
this[IS_THUNK] = true;
this.func = func;
this.result = null;
}
}
const is_thunk = (value) => {
return value !== null && typeof value === "object" && value[IS_THUNK] === true;
};
const force = (value) => {
if (!is_thunk(value)) {
return value;
}
if (value.func === null) {
return value.result;
}
const result = force(value.func());
value.result = result;
value.func = null;
return result;
};
const create_thunk = (func) => new NixThunk(func);
const create_lazy_set = (definitions) => {
const cache = new Map();
return new Proxy({}, {
get: (_target, key) => {
if (cache.has(key)) {
return cache.get(key);
}
if (key in definitions) {
const value = definitions[key]();
cache.set(key, value);
return value;
}
return undefined;
}
});
};
const select = (obj, key) => {
const forced_obj = force(obj);
const forced_key = force(key);
if (forced_obj === null || forced_obj === undefined) {
throw new Error(`Cannot select '${forced_key}' from null/undefined`);
}
if (!(forced_key in forced_obj)) {
throw new Error(`Attribute '${forced_key}' not found`);
}
return forced_obj[forced_key];
};
const select_with_default = (obj, key, default_val) => {
const forced_obj = force(obj);
const forced_key = force(key);
if (forced_obj === null || forced_obj === undefined) {
return default_val;
}
if (!(forced_key in forced_obj)) {
return default_val;
}
return forced_obj[forced_key];
};
const validate_params = (arg, required, allowed) => {
const forced_arg = force(arg);
// Check required parameters
if (required) {
for (const key of required) {
if (!Object.prototype.hasOwnProperty.call(forced_arg, key)) {
throw new Error(`Function called without required argument '${key}'`);
}
}
}
// Check allowed parameters (if not using ellipsis)
if (allowed) {
const allowed_set = new Set(allowed);
for (const key in forced_arg) {
if (!allowed_set.has(key)) {
throw new Error(`Function called with unexpected argument '${key}'`);
}
}
}
return forced_arg;
};
const op = {
add: (a, b) => force(a) + force(b),
sub: (a, b) => force(a) - force(b),
mul: (a, b) => force(a) * force(b),
div: (a, b) => force(a) / force(b),
eq: (a, b) => force(a) === force(b),
neq: (a, b) => force(a) !== force(b),
lt: (a, b) => force(a) < force(b),
lte: (a, b) => force(a) <= force(b),
gt: (a, b) => force(a) > force(b),
gte: (a, b) => force(a) >= force(b),
band: (a, b) => force(a) && force(b),
bor: (a, b) => force(a) || force(b),
bnot: (a) => !force(a),
concat: (a, b) => Array.prototype.concat.apply(force(a), force(b)),
update: (a, b) => ({ ...force(a), ...force(b) }),
};
const builtins = {
add: (a) => (b) => force(a) + force(b),
sub: (a) => (b) => force(a) - force(b),
mul: (a) => (b) => force(a) * force(b),
div: (a) => (b) => force(a) / force(b),
bitAnd: (a) => (b) => force(a) & force(b),
bitOr: (a) => (b) => force(a) | force(b),
bitXor: (a) => (b) => force(a) ^ force(b),
lessThan: (a) => (b) => force(a) < force(b),
ceil: (x) => Math.ceil(force(x)),
floor: (x) => Math.floor(force(x)),
deepSeq: (e1) => (e2) => {
throw "Not implemented: deepSeq"
},
seq: (e1) => (e2) => {
force(e1);
return e2;
},
fetchClosure: (args) => {
throw "Not implemented: fetchClosure"
},
fetchGit: (args) => {
throw "Not implemented: fetchGit"
},
fetchTarball: (args) => {
throw "Not implemented: fetchTarball"
},
fetchTree: (args) => {
throw "Not implemented: fetchTree"
},
fetchurl: (args) => {
throw "Not implemented: fetchurl"
},
fromJSON: (e) => {
throw "Not implemented: fromJSON"
},
fromTOML: (e) => {
throw "Not implemented: fromTOML"
},
toJSON: (e) => {
throw "Not implemented: toJSON"
},
toXML: (e) => {
throw "Not implemented: toXML"
},
getContext: (s) => {
throw "Not implemented: getContext"
},
hasContext: (s) => {
throw "Not implemented: hasContext"
},
hashFile: (type) => (p) => {
throw "Not implemented: hashFile"
},
hashString: (type) => (p) => {
throw "Not implemented: hashString"
},
convertHash: (args) => {
throw "Not implemented: convertHash"
},
isAttrs: (e) => {
const val = force(e);
return typeof val === "object" && !Array.isArray(val) && val !== null;
},
isBool: (e) => typeof force(e) === "boolean",
isFloat: (e) => {
throw "Not implemented: isFloat"
},
isFunction: (e) => typeof force(e) === "function",
isInt: (e) => {
throw "Not implemented: isInt"
},
isList: (e) => Array.isArray(force(e)),
isNull: (e) => force(e) === null,
isPath: (e) => {
throw "Not implemented: isPath"
},
isString: (e) => typeof force(e) === "string",
typeOf: (e) => {
throw "Not implemented: typeOf"
},
import: (path) => {
throw "Not implemented: import"
},
scopedImport: (scope) => (path) => {
throw "Not implemented: scopedImport"
},
unsafeDiscardOutputDependency: (s) => {
throw "Not implemented: unsafeDiscardOutputDependency"
},
unsafeDiscardStringContext: (s) => {
throw "Not implemented: unsafeDiscardStringContext"
},
unsafeGetAttrPos: (s) => {
throw "Not implemented: unsafeGetAttrPos"
},
abort: (s) => {
throw `evaluation aborted with the following error message: '${force(s)}'`
},
addDrvOutputDependencies: (s) => {
throw "Not implemented: addDrvOutputDependencies"
},
all: (pred) => (list) => Array.prototype.every.call(force(list), force(pred)),
any: (pred) => (list) => Array.prototype.some.call(force(list), force(pred)),
attrNames: (set) => Object.keys(force(set)),
attrValues: (set) => Object.values(force(set)),
baseNameOf: (x) => {
const str = force(x);
if (str.length === 0)
return "";
let last = str.length - 1;
if (str[last] === "/" && last > 0)
last -= 1;
let pos = last;
while (pos >= 0 && str[pos] !== "/")
pos -= 1;
if (pos !== 0 || (pos === 0 && str[pos] === "/")) {
pos += 1;
}
return String.prototype.substring.call(str, pos, last + 1);
},
break: (v) => v,
catAttrs: (attr) => (list) => {
const key = force(attr);
return force(list).map((set) => force(set)[key]).filter((val) => val !== undefined);
},
compareVersions: (s1) => (s2) => {
throw "Not implemented: compareVersions"
},
concatLists: (lists) => Array.prototype.reduce.call(force(lists), (acc, cur) => Array.prototype.concat(acc, force(cur)), []),
concatMap: (f) => (lists) => {
const fn = force(f);
return Array.prototype.reduce.call(force(lists), (acc, cur) => Array.prototype.concat(acc, force(fn(cur))), []);
},
concatStringsSep: (sep) => (list) => Array.prototype.join.call(force(list), force(sep)),
dirOf: (s) => {
throw "Not implemented: dirOf"
},
elem: (x) => (xs) => Array.prototype.includes.call(force(xs), force(x)),
elemAt: (xs) => (n) => force(xs)[force(n)],
filter: (f) => (list) => Array.prototype.filter.call(force(list), force(f)),
filterSource: (args) => {
throw "Not implemented: filterSource"
},
findFile: (search) => (lookup) => {
throw "Not implemented: findFile"
},
flakeRefToString: (attrs) => {
throw "Not implemented: flakeRefToString"
},
["foldl'"]:(op_fn) => (nul) => (list) => {
const forced_op = force(op_fn);
return Array.prototype.reduce.call(force(list), (acc, cur) => forced_op(acc)(cur), nul);
},
functionArgs: (f) => {
throw "Not implemented: functionArgs"
},
genList: (f) => (len) => {
const forced_f = force(f);
const forced_len = force(len);
return [...Array(forced_len).keys()].map(i => forced_f(i));
},
genericClosure: (args) => {
throw "Not implemented: genericClosure"
},
getAttr: (s) => (set) => force(set)[force(s)],
getEnv: (s) => {
throw "Not implemented: getEnv"
},
getFlake: (attrs) => {
throw "Not implemented: getFlake"
},
groupBy: (f) => (list) => {
let attrs = {};
const forced_f = force(f);
const forced_list = force(list);
for (const elem of forced_list) {
const key = force(forced_f(elem));
if (!attrs[key]) attrs[key] = [];
attrs[key].push(elem);
}
return attrs;
},
hasAttr: (s) => (set) => Object.prototype.hasOwnProperty.call(force(set), force(s)),
head: (list) => force(list)[0],
intersectAttrs: (e1) => (e2) => {
const f1 = force(e1);
const f2 = force(e2);
let attrs = {};
for (const key of Object.keys(f2)) {
if (Object.prototype.hasOwnProperty.call(f1, key)) {
attrs[key] = f2[key];
}
}
return attrs;
},
length: (e) => force(e).length,
listToAttrs: (e) => {
let attrs = {};
const forced_e = [...force(e)].reverse();
for (const obj of forced_e) {
const item = force(obj);
attrs[force(item.name)] = item.value;
}
return attrs;
},
map: (f) => (list) => Array.prototype.map.call(force(list), force(f)),
mapAttrs: (f) => (attrs) => {
let new_attrs = {};
const forced_attrs = force(attrs);
const forced_f = force(f);
for (const key in forced_attrs) {
new_attrs[key] = forced_f(key)(forced_attrs[key]);
}
return new_attrs;
},
match: (regex) => (str) => {
throw "Not implemented: match"
},
outputOf: (drv) => (out) => {
throw "Not implemented: outputOf"
},
parseDrvName: (s) => {
throw "Not implemented: parseDrvName"
},
parseFlakeName: (s) => {
throw "Not implemented: parseFlakeName"
},
partition: (pred) => (list) => {
const forced_list = force(list);
const forced_pred = force(pred);
let attrs = {
right: [],
wrong: [],
};
for (const elem of forced_list) {
if (force(forced_pred(elem))) {
attrs.right.push(elem);
} else {
attrs.wrong.push(elem);
}
}
return attrs;
},
path: (args) => {
throw "Not implemented: path"
},
pathExists: (path) => {
throw "Not implemented: pathExists"
},
placeholder: (output) => {
throw "Not implemented: placeholder"
},
readDir: (path) => {
throw "Not implemented: readDir"
},
readFile: (path) => {
throw "Not implemented: readFile"
},
readFileType: (path) => {
throw "Not implemented: readFileType"
},
replaceStrings: (from) => (to) => (s) => {
throw "Not implemented: replaceStrings"
},
sort: (cmp) => (list) => {
const forced_list = [...force(list)];
const forced_cmp = force(cmp);
return forced_list.sort((a, b) => {
if (force(forced_cmp(a)(b))) return -1;
if (force(forced_cmp(b)(a))) return 1;
return 0;
});
},
split: (regex, str) => {
throw "Not implemented: split"
},
splitVersion: (s) => {
throw "Not implemented: splitVersion"
},
stringLength: (e) => force(e).length,
substring: (start) => (len) => (s) => String.prototype.substring.call(force(s), force(start), force(start) + force(len)),
tail: (list) => Array.prototype.slice.call(force(list), 1),
throw: (s) => {
throw force(s);
},
toFile: (name, s) => {
throw "Not implemented: toFile"
},
toPath: (name, s) => {
throw "Not implemented: toPath"
},
toString: (name, s) => {
throw "Not implemented: toString"
},
trace: (e1, e2) => {
console.log(`trace: ${force(e1)}`);
return e2;
},
traceVerbose: (e1, e2) => {
throw "Not implemented: traceVerbose"
},
tryEval: (e1) => (e2) => {
throw "Not implemented: tryEval"
},
warn: (e1) => (e2) => {
console.log(`evaluation warning: ${force(e1)}`);
return e2;
},
zipAttrsWith: (f) => (list) => {
throw "Not implemented: zipAttrsWith"
},
builtins: create_thunk(() => builtins),
currentSystem: create_thunk(() => {
throw "Not implemented: currentSystem"
}),
currentTime: create_thunk(() => Date.now()),
[false]: false,
[true]: true,
[null]: null,
langVersion: 6,
nixPath: [],
nixVersion: "NIX_JS_VERSION",
storeDir: "/nix/store",
};
return {
create_thunk,
force,
is_thunk,
create_lazy_set,
select,
select_with_default,
validate_params,
op,
builtins,
IS_THUNK // Export the Symbol for Rust to use
};
})();
// Ensure Nix is available on the global object for Rust access
globalThis.Nix = Nix;