feat: rewrite runtime.js with typescript
This commit is contained in:
@@ -25,6 +25,10 @@
|
|||||||
lldb
|
lldb
|
||||||
valgrind
|
valgrind
|
||||||
claude-code
|
claude-code
|
||||||
|
|
||||||
|
# Node.js tooling for TypeScript build
|
||||||
|
nodejs
|
||||||
|
nodePackages.npm
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "nix-js"
|
name = "nix-js"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|||||||
67
nix-js/build.rs
Normal file
67
nix-js/build.rs
Normal 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
2
nix-js/runtime-ts/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/node_modules
|
||||||
|
/dist
|
||||||
496
nix-js/runtime-ts/package-lock.json
generated
Normal file
496
nix-js/runtime-ts/package-lock.json
generated
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
nix-js/runtime-ts/package.json
Normal file
14
nix-js/runtime-ts/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
53
nix-js/runtime-ts/src/builtins/arithmetic.ts
Normal file
53
nix-js/runtime-ts/src/builtins/arithmetic.ts
Normal 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);
|
||||||
67
nix-js/runtime-ts/src/builtins/attrs.ts
Normal file
67
nix-js/runtime-ts/src/builtins/attrs.ts
Normal 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;
|
||||||
|
};
|
||||||
25
nix-js/runtime-ts/src/builtins/conversion.ts
Normal file
25
nix-js/runtime-ts/src/builtins/conversion.ts
Normal 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';
|
||||||
|
};
|
||||||
35
nix-js/runtime-ts/src/builtins/functional.ts
Normal file
35
nix-js/runtime-ts/src/builtins/functional.ts
Normal 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;
|
||||||
169
nix-js/runtime-ts/src/builtins/index.ts
Normal file
169
nix-js/runtime-ts/src/builtins/index.ts
Normal 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',
|
||||||
|
};
|
||||||
74
nix-js/runtime-ts/src/builtins/io.ts
Normal file
74
nix-js/runtime-ts/src/builtins/io.ts
Normal 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';
|
||||||
|
};
|
||||||
102
nix-js/runtime-ts/src/builtins/list.ts
Normal file
102
nix-js/runtime-ts/src/builtins/list.ts
Normal 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));
|
||||||
18
nix-js/runtime-ts/src/builtins/math.ts
Normal file
18
nix-js/runtime-ts/src/builtins/math.ts
Normal 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
|
||||||
|
};
|
||||||
109
nix-js/runtime-ts/src/builtins/misc.ts
Normal file
109
nix-js/runtime-ts/src/builtins/misc.ts
Normal 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';
|
||||||
|
};
|
||||||
35
nix-js/runtime-ts/src/builtins/string.ts
Normal file
35
nix-js/runtime-ts/src/builtins/string.ts
Normal 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);
|
||||||
|
};
|
||||||
50
nix-js/runtime-ts/src/builtins/type-check.ts
Normal file
50
nix-js/runtime-ts/src/builtins/type-check.ts
Normal 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}`);
|
||||||
|
};
|
||||||
99
nix-js/runtime-ts/src/helpers.ts
Normal file
99
nix-js/runtime-ts/src/helpers.ts
Normal 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;
|
||||||
|
};
|
||||||
34
nix-js/runtime-ts/src/index.ts
Normal file
34
nix-js/runtime-ts/src/index.ts
Normal 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
|
||||||
|
}
|
||||||
91
nix-js/runtime-ts/src/operators.ts
Normal file
91
nix-js/runtime-ts/src/operators.ts
Normal 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) };
|
||||||
|
},
|
||||||
|
};
|
||||||
79
nix-js/runtime-ts/src/thunk.ts
Normal file
79
nix-js/runtime-ts/src/thunk.ts
Normal 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);
|
||||||
|
};
|
||||||
127
nix-js/runtime-ts/src/type-assert.ts
Normal file
127
nix-js/runtime-ts/src/type-assert.ts
Normal 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];
|
||||||
|
};
|
||||||
55
nix-js/runtime-ts/src/types.ts
Normal file
55
nix-js/runtime-ts/src/types.ts
Normal 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;
|
||||||
21
nix-js/runtime-ts/tsconfig.json
Normal file
21
nix-js/runtime-ts/tsconfig.json
Normal 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"]
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
match self {
|
match self {
|
||||||
Ir::Const(Const { val }) => match val {
|
Ir::Const(Const { val }) => match val {
|
||||||
crate::value::Const::Null => "null".to_string(),
|
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::Float(val) => val.to_string(),
|
||||||
crate::value::Const::Bool(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),
|
Gt => format!("Nix.op.gt({},{})", lhs, rhs),
|
||||||
Leq => format!("Nix.op.lte({},{})", lhs, rhs),
|
Leq => format!("Nix.op.lte({},{})", lhs, rhs),
|
||||||
Geq => format!("Nix.op.gte({},{})", lhs, rhs),
|
Geq => format!("Nix.op.gte({},{})", lhs, rhs),
|
||||||
And => format!("Nix.op.band({},{})", lhs, rhs),
|
// Short-circuit operators: use JavaScript native && and ||
|
||||||
Or => format!("Nix.op.bor({},{})", lhs, rhs),
|
And => format!("(Nix.force({}) && Nix.force({}))", lhs, rhs),
|
||||||
Impl => format!("Nix.op.bor(Nix.op.bnot({}),{})", 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),
|
Con => format!("Nix.op.concat({},{})", lhs, rhs),
|
||||||
Upd => format!("Nix.op.update({},{})", lhs, rhs),
|
Upd => format!("Nix.op.update({},{})", lhs, rhs),
|
||||||
PipeL => format!("Nix.force({})({})", rhs, lhs),
|
PipeL => format!("Nix.force({})({})", rhs, lhs),
|
||||||
@@ -84,7 +85,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
|
|||||||
use UnOpKind::*;
|
use UnOpKind::*;
|
||||||
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Neg => format!("Nix.op.sub(0,{rhs})"),
|
Neg => format!("Nix.op.sub(0n,{rhs})"),
|
||||||
Not => format!("Nix.op.bnot({rhs})"),
|
Not => format!("Nix.op.bnot({rhs})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ mod test {
|
|||||||
("2 > 1", Value::Const(Const::Bool(true))),
|
("2 > 1", Value::Const(Const::Bool(true))),
|
||||||
("1 <= 1", Value::Const(Const::Bool(true))),
|
("1 <= 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(true))),
|
||||||
("true && 1 == 0", Value::Const(Const::Bool(false))),
|
("true && 1 == 0", Value::Const(Const::Bool(false))),
|
||||||
(
|
(
|
||||||
@@ -560,4 +561,148 @@ mod test {
|
|||||||
Value::Const(Const::Bool(false))
|
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))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 context = v8::Context::new(handle_scope, v8::ContextOptions::default());
|
||||||
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
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_source = v8::String::new(scope, runtime_code).unwrap();
|
||||||
let runtime_script = v8::Script::compile(scope, runtime_source, None).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>,
|
scope: &v8::PinnedRef<'a, v8::HandleScope>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
match () {
|
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() => {
|
_ if val.is_big_int() => {
|
||||||
let (val, true) = val.to_big_int(scope).unwrap().i64_value() else {
|
let (val, lossless) = val.to_big_int(scope).unwrap().i64_value();
|
||||||
todo!()
|
if !lossless {
|
||||||
};
|
panic!("BigInt value out of i64 range: conversion lost precision");
|
||||||
|
}
|
||||||
Value::Const(Const::Int(val))
|
Value::Const(Const::Int(val))
|
||||||
}
|
}
|
||||||
_ if val.is_number() => {
|
_ if val.is_number() => {
|
||||||
let val = val.to_number(scope).unwrap().value();
|
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_true() => Value::Const(Const::Bool(true)),
|
||||||
_ if val.is_false() => Value::Const(Const::Bool(false)),
|
_ if val.is_false() => Value::Const(Const::Bool(false)),
|
||||||
|
|||||||
@@ -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;
|
|
||||||
Reference in New Issue
Block a user