Compare commits
4 Commits
a8f1c81b60
...
48a43bed55
| Author | SHA1 | Date | |
|---|---|---|---|
| 48a43bed55 | |||
| 6d8f1e79e6 | |||
| df4edaf5bb | |||
| 3aee3c67b9 |
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -1013,12 +1013,6 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fixedbitset"
|
|
||||||
version = "0.5.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.5"
|
version = "1.1.5"
|
||||||
@@ -1957,7 +1951,6 @@ dependencies = [
|
|||||||
"nix-js-macros",
|
"nix-js-macros",
|
||||||
"nix-nar",
|
"nix-nar",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"petgraph",
|
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rnix",
|
"rnix",
|
||||||
@@ -1984,7 +1977,6 @@ name = "nix-js-macros"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case 0.8.0",
|
"convert_case 0.8.0",
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
@@ -2170,18 +2162,6 @@ version = "2.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "petgraph"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
|
||||||
dependencies = [
|
|
||||||
"fixedbitset",
|
|
||||||
"hashbrown 0.15.5",
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.10"
|
version = "1.1.10"
|
||||||
|
|||||||
8
Justfile
8
Justfile
@@ -1,15 +1,15 @@
|
|||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@repl:
|
@repl:
|
||||||
RUST_LOG=none,nix_js=debug cargo run --bin repl
|
RUST_LOG=none cargo run --bin repl
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@eval expr:
|
@eval expr:
|
||||||
RUST_LOG=none,nix_js=debug cargo run --bin eval -- '{{expr}}'
|
RUST_LOG=none cargo run --bin eval -- '{{expr}}'
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@replr:
|
@replr:
|
||||||
RUST_LOG=info cargo run --bin repl --release
|
RUST_LOG=none cargo run --bin repl --release
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@evalr expr:
|
@evalr expr:
|
||||||
RUST_LOG=silent cargo run --bin eval --release -- '{{expr}}'
|
RUST_LOG=none cargo run --bin eval --release -- '{{expr}}'
|
||||||
|
|||||||
18
flake.lock
generated
18
flake.lock
generated
@@ -8,11 +8,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770447430,
|
"lastModified": 1770966612,
|
||||||
"narHash": "sha256-smrRbWhvJF6BATB6pXbD8Cp04HRrVcYQkXqOhUF81nk=",
|
"narHash": "sha256-S6k14z/JsDwX6zZyLucDBTOe/9RsvxH9GTUxHn2o4vc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "e1b28f6ca0d1722edceec1f2f3501558988d1aed",
|
"rev": "e90d48dcfaebac7ea7a5687888a2d0733be26343",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770197578,
|
"lastModified": 1770841267,
|
||||||
"narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=",
|
"narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2",
|
"rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -61,11 +61,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770290336,
|
"lastModified": 1770934477,
|
||||||
"narHash": "sha256-rJ79U68ZLjCSg1Qq+63aBXi//W7blaKiYq9NnfeTboA=",
|
"narHash": "sha256-GX0cINHhhzUbQHyDYN2Mc+ovb6Sx/4yrF95VVou9aW4=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "d2a00da09293267e5be2efb216698762929d7140",
|
"rev": "931cd553be123b11db1435ac7ea5657e62e5e601",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"rust-analyzer"
|
"rust-analyzer"
|
||||||
])
|
])
|
||||||
cargo-outdated
|
cargo-outdated
|
||||||
|
cargo-machete
|
||||||
lldb
|
lldb
|
||||||
valgrind
|
valgrind
|
||||||
hyperfine
|
hyperfine
|
||||||
|
|||||||
@@ -9,5 +9,4 @@ proc-macro = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
convert_case = "0.8"
|
convert_case = "0.8"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
|
||||||
syn = { version = "2.0", features = ["full"] }
|
syn = { version = "2.0", features = ["full"] }
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ thiserror = "2"
|
|||||||
miette = { version = "7.4", features = ["fancy"] }
|
miette = { version = "7.4", features = ["fancy"] }
|
||||||
|
|
||||||
hashbrown = "0.16"
|
hashbrown = "0.16"
|
||||||
petgraph = "0.8"
|
|
||||||
string-interner = "0.19"
|
string-interner = "0.19"
|
||||||
|
|
||||||
rust-embed="8.11"
|
rust-embed="8.11"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Conversion and serialization builtin functions
|
* Conversion and serialization builtin functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { NixValue, NixString, NixPath } from "../types";
|
import type { NixString, NixValue } from "../types";
|
||||||
import { isStringWithContext, isNixPath } from "../types";
|
import { isStringWithContext, isNixPath } from "../types";
|
||||||
import { force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
||||||
@@ -59,7 +59,7 @@ export const fromTOML = (e: NixValue): never => {
|
|||||||
|
|
||||||
export const toJSON = (e: NixValue): NixString => {
|
export const toJSON = (e: NixValue): NixString => {
|
||||||
const context: Set<string> = new Set();
|
const context: Set<string> = new Set();
|
||||||
const string = JSON.stringify(nixValueToJson(e, new Set(), context));
|
const string = JSON.stringify(nixValueToJson(e, true, context, true));
|
||||||
if (context.size === 0) {
|
if (context.size === 0) {
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NixValue, NixAttrs } from "../types";
|
import type { NixValue, NixAttrs } from "../types";
|
||||||
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
|
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
|
||||||
import { force, createThunk } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import {
|
import {
|
||||||
type DerivationData,
|
type DerivationData,
|
||||||
type OutputInfo,
|
type OutputInfo,
|
||||||
@@ -99,17 +99,6 @@ const extractOutputs = (attrs: NixAttrs, structuredAttrs: boolean): string[] =>
|
|||||||
return outputs;
|
return outputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractOutputsForWrapper = (attrs: NixAttrs): string[] => {
|
|
||||||
if (!("outputs" in attrs)) {
|
|
||||||
return ["out"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: trace context?
|
|
||||||
const outputs = forceList(attrs.outputs).map(forceStringValue);
|
|
||||||
validateOutputs(outputs);
|
|
||||||
return outputs;
|
|
||||||
};
|
|
||||||
|
|
||||||
const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] => {
|
const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] => {
|
||||||
if (!("args" in attrs)) {
|
if (!("args" in attrs)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -126,12 +115,7 @@ const structuredAttrsExcludedKeys = new Set([
|
|||||||
"args",
|
"args",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const specialAttrs = new Set([
|
const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]);
|
||||||
"args",
|
|
||||||
"__ignoreNulls",
|
|
||||||
"__contentAddressed",
|
|
||||||
"__impure",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const sortedJsonStringify = (obj: Record<string, any>): string => {
|
const sortedJsonStringify = (obj: Record<string, any>): string => {
|
||||||
const sortedKeys = Object.keys(obj).sort();
|
const sortedKeys = Object.keys(obj).sort();
|
||||||
@@ -159,7 +143,7 @@ const extractEnv = (
|
|||||||
if (ignoreNulls && forcedValue === null) {
|
if (ignoreNulls && forcedValue === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
jsonAttrs[key] = nixValueToJson(value, new Set(), outContext);
|
jsonAttrs[key] = nixValueToJson(value, true, outContext, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "allowedReferences") {
|
if (key === "allowedReferences") {
|
||||||
@@ -295,7 +279,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
const drvArgs = extractArgs(attrs, collectedContext);
|
const drvArgs = extractArgs(attrs, collectedContext);
|
||||||
const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext, drvName);
|
const env = extractEnv(attrs, structuredAttrs, ignoreNulls, collectedContext, drvName);
|
||||||
|
|
||||||
|
|
||||||
const { inputDrvs, inputSrcs } = extractInputDrvsAndSrcs(collectedContext);
|
const { inputDrvs, inputSrcs } = extractInputDrvsAndSrcs(collectedContext);
|
||||||
|
|
||||||
const collectDrvReferences = (): string[] => {
|
const collectDrvReferences = (): string[] => {
|
||||||
@@ -429,47 +412,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const derivation = (args: NixValue): NixAttrs => {
|
export const derivation = (_: NixValue): NixAttrs => {
|
||||||
const attrs = forceAttrs(args);
|
throw new Error("unreachable: placeholder derivation implementation called")
|
||||||
|
|
||||||
const outputs: string[] = extractOutputsForWrapper(attrs);
|
|
||||||
|
|
||||||
const strictThunk = createThunk(() => derivationStrict(args), "derivationStrict");
|
|
||||||
|
|
||||||
const commonAttrs: NixAttrs = { ...attrs };
|
|
||||||
|
|
||||||
const outputToAttrListElement = (outputName: string): { name: string; value: NixAttrs } => {
|
|
||||||
const value: NixAttrs = {
|
|
||||||
...commonAttrs,
|
|
||||||
outPath: createThunk(() => (force(strictThunk) as NixAttrs)[outputName], `outPath_${outputName}`),
|
|
||||||
drvPath: createThunk(() => (force(strictThunk) as NixAttrs).drvPath, "drvPath"),
|
|
||||||
type: "derivation",
|
|
||||||
outputName,
|
|
||||||
};
|
|
||||||
return { name: outputName, value };
|
|
||||||
};
|
|
||||||
|
|
||||||
const outputsList = outputs.map(outputToAttrListElement);
|
|
||||||
|
|
||||||
for (const { name: outputName } of outputsList) {
|
|
||||||
commonAttrs[outputName] = createThunk(
|
|
||||||
() => outputsList.find((o) => o.name === outputName)!.value,
|
|
||||||
`output_${outputName}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
commonAttrs.all = createThunk(() => outputsList.map((o) => o.value), "all_outputs");
|
|
||||||
commonAttrs.drvAttrs = attrs;
|
|
||||||
|
|
||||||
for (const { value: outputObj } of outputsList) {
|
|
||||||
for (const { name: outputName } of outputsList) {
|
|
||||||
outputObj[outputName] = createThunk(
|
|
||||||
() => outputsList.find((o) => o.name === outputName)!.value,
|
|
||||||
`output_${outputName}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
outputObj.all = createThunk(() => outputsList.map((o) => o.value), "all_outputs");
|
|
||||||
outputObj.drvAttrs = attrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputsList[0].value;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,18 +87,17 @@ export const fetchClosure = (args: NixValue): never => {
|
|||||||
throw new Error("Not implemented: fetchClosure");
|
throw new Error("Not implemented: fetchClosure");
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FetchUrlResult {
|
export interface FetchUrlResult {
|
||||||
store_path: string;
|
store_path: string;
|
||||||
hash: string;
|
hash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FetchTarballResult {
|
export interface FetchTarballResult {
|
||||||
store_path: string;
|
store_path: string;
|
||||||
hash: string;
|
|
||||||
nar_hash: string;
|
nar_hash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FetchGitResult {
|
export interface FetchGitResult {
|
||||||
out_path: string;
|
out_path: string;
|
||||||
rev: string;
|
rev: string;
|
||||||
short_rev: string;
|
short_rev: string;
|
||||||
@@ -109,7 +108,7 @@ interface FetchGitResult {
|
|||||||
nar_hash: string | null;
|
nar_hash: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FetchHgResult {
|
export interface FetchHgResult {
|
||||||
out_path: string;
|
out_path: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
rev: string;
|
rev: string;
|
||||||
@@ -137,16 +136,11 @@ const normalizeUrlInput = (
|
|||||||
return { url, hash, name, executable };
|
return { url, hash, name, executable };
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeTarballInput = (
|
const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string; name?: string } => {
|
||||||
args: NixValue,
|
|
||||||
): { url: string; sha256?: string; name?: string } => {
|
|
||||||
const forced = force(args);
|
const forced = force(args);
|
||||||
if (isAttrs(forced)) {
|
if (isAttrs(forced)) {
|
||||||
const url = forceStringNoCtx(forced.url);
|
const url = forceStringNoCtx(forced.url);
|
||||||
const sha256 =
|
const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined;
|
||||||
"sha256" in forced
|
|
||||||
? forceStringNoCtx(forced.sha256)
|
|
||||||
: undefined;
|
|
||||||
const name = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
const name = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
||||||
return { url, sha256, name };
|
return { url, sha256, name };
|
||||||
} else {
|
} else {
|
||||||
@@ -167,11 +161,7 @@ export const fetchurl = (args: NixValue): string => {
|
|||||||
|
|
||||||
export const fetchTarball = (args: NixValue): string => {
|
export const fetchTarball = (args: NixValue): string => {
|
||||||
const { url, name, sha256 } = normalizeTarballInput(args);
|
const { url, name, sha256 } = normalizeTarballInput(args);
|
||||||
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(
|
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null);
|
||||||
url,
|
|
||||||
name ?? null,
|
|
||||||
sha256 ?? null,
|
|
||||||
);
|
|
||||||
return result.store_path;
|
return result.store_path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +1,82 @@
|
|||||||
import { HAS_CONTEXT, NixStringContext } from "./string-context";
|
import { HAS_CONTEXT, NixStringContext } from "./string-context";
|
||||||
import { force } from "./thunk";
|
import { force, isThunk } from "./thunk";
|
||||||
import type { NixValue } from "./types";
|
import type { NixValue } from "./types";
|
||||||
import { isStringWithContext } from "./types";
|
import { isStringWithContext, IS_PATH } from "./types";
|
||||||
|
|
||||||
export const nixValueToJson = (
|
export const nixValueToJson = (
|
||||||
value: NixValue,
|
value: NixValue,
|
||||||
seen = new Set<object>(),
|
strict: boolean,
|
||||||
outContext?: NixStringContext,
|
outContext: NixStringContext,
|
||||||
|
copyToStore: boolean,
|
||||||
|
seen: Set<NixValue> = new Set(),
|
||||||
): any => {
|
): any => {
|
||||||
const v = force(value);
|
const v = strict ? force(value) : value;
|
||||||
|
|
||||||
|
if (isThunk(v) || typeof v === "function")
|
||||||
|
throw new Error(`cannot convert ${isThunk(v) ? "thunk" : "lambda"} to JSON`);
|
||||||
if (v === null) return null;
|
if (v === null) return null;
|
||||||
if (typeof v === "boolean") return v;
|
|
||||||
if (typeof v === "string") return v;
|
|
||||||
if (typeof v === "number") return v;
|
|
||||||
|
|
||||||
if (typeof v === "object" && HAS_CONTEXT in v && "context" in v) {
|
|
||||||
if (outContext) {
|
|
||||||
for (const elem of v.context) {
|
|
||||||
outContext.add(elem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof v === "bigint") {
|
if (typeof v === "bigint") {
|
||||||
const num = Number(v);
|
const num = Number(v);
|
||||||
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
|
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
|
||||||
console.warn(`derivation: integer ${v} exceeds safe range, precision may be lost in __structuredAttrs`);
|
console.warn(`integer ${v} exceeds safe range, precision may be lost`);
|
||||||
}
|
}
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
if (typeof v === "number") return v;
|
||||||
if (typeof v === "object" && v !== null) {
|
if (typeof v === "boolean") return v;
|
||||||
if (seen.has(v)) {
|
if (typeof v === "string") return v;
|
||||||
throw new Error("derivation: circular reference detected in __structuredAttrs");
|
if (typeof v === "object" && HAS_CONTEXT in v) {
|
||||||
|
for (const elem of v.context) {
|
||||||
|
outContext.add(elem);
|
||||||
}
|
}
|
||||||
seen.add(v);
|
return v.value;
|
||||||
|
}
|
||||||
|
if (typeof v === "object" && IS_PATH in v) {
|
||||||
|
if (copyToStore) {
|
||||||
|
const storePath = Deno.core.ops.op_copy_path_to_store(v.value);
|
||||||
|
outContext.add(storePath);
|
||||||
|
return storePath;
|
||||||
|
} else {
|
||||||
|
return v.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seen.has(v)) {
|
||||||
|
throw new Error("cycle detected in toJSON");
|
||||||
|
} else {
|
||||||
|
seen.add(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(v)) {
|
if (Array.isArray(v)) {
|
||||||
return v.map((item) => nixValueToJson(item, seen, outContext));
|
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof v === "object") {
|
// NixAttrs
|
||||||
if ("__toString" in v && typeof force(v.__toString) === "function") {
|
if ("__toString" in v && typeof force(v.__toString) === "function") {
|
||||||
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
|
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
|
||||||
const result = force(toStringMethod(v));
|
const result = force(toStringMethod(v));
|
||||||
if (typeof result === "string") {
|
if (typeof result === "string") {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (isStringWithContext(result)) {
|
if (isStringWithContext(result)) {
|
||||||
if (outContext) {
|
if (outContext) {
|
||||||
for (const elem of result.context) {
|
for (const elem of result.context) {
|
||||||
outContext.add(elem);
|
outContext.add(elem);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result.value;
|
|
||||||
}
|
}
|
||||||
return nixValueToJson(result, seen, outContext);
|
return result.value;
|
||||||
}
|
}
|
||||||
|
return nixValueToJson(result, strict, outContext, copyToStore, seen);
|
||||||
if ("outPath" in v) {
|
|
||||||
return nixValueToJson(v.outPath, seen, outContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: Record<string, any> = {};
|
|
||||||
const keys = Object.keys(v).sort();
|
|
||||||
for (const key of keys) {
|
|
||||||
result[key] = nixValueToJson((v as Record<string, NixValue>)[key], seen, outContext);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof v === "function") {
|
if ("outPath" in v) {
|
||||||
throw new Error("derivation: cannot serialize function in __structuredAttrs");
|
return nixValueToJson(v.outPath, strict, outContext, copyToStore,seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`derivation: cannot serialize ${typeof v} to JSON`);
|
const result: Record<string, any> = {};
|
||||||
|
const keys = Object.keys(v).sort();
|
||||||
|
for (const key of keys) {
|
||||||
|
result[key] = nixValueToJson(v[key], strict, outContext, copyToStore, seen);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ export const escapeString = (s: string): string => {
|
|||||||
|
|
||||||
const quoteString = (s: string): string => `"${s}"`;
|
const quoteString = (s: string): string => `"${s}"`;
|
||||||
|
|
||||||
const cmpByKey = <T>(a: [string, T], b: [string, T]): number =>
|
const cmpByKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
||||||
a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
||||||
|
|
||||||
export const generateAterm = (drv: DerivationData): string => {
|
export const generateAterm = (drv: DerivationData): string => {
|
||||||
const outputEntries: string[] = [];
|
const outputEntries: string[] = [];
|
||||||
|
|||||||
@@ -380,3 +380,20 @@ export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => {
|
|||||||
export const mkPos = (span: string): NixAttrs => {
|
export const mkPos = (span: string): NixAttrs => {
|
||||||
return Deno.core.ops.op_decode_span(span);
|
return Deno.core.ops.op_decode_span(span);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface WithScope {
|
||||||
|
env: NixValue;
|
||||||
|
last: WithScope | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lookupWith = (name: string, withScope: WithScope | null): NixValue => {
|
||||||
|
let current = withScope;
|
||||||
|
while (current !== null) {
|
||||||
|
const attrs = forceAttrs(current.env);
|
||||||
|
if (name in attrs) {
|
||||||
|
return attrs[name];
|
||||||
|
}
|
||||||
|
current = current.last;
|
||||||
|
}
|
||||||
|
throw new Error(`undefined variable '${name}'`);
|
||||||
|
};
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
popContext,
|
popContext,
|
||||||
withContext,
|
withContext,
|
||||||
mkPos,
|
mkPos,
|
||||||
|
lookupWith,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import { op } from "./operators";
|
import { op } from "./operators";
|
||||||
import { builtins, PRIMOP_METADATA } from "./builtins";
|
import { builtins, PRIMOP_METADATA } from "./builtins";
|
||||||
@@ -60,6 +61,7 @@ export const Nix = {
|
|||||||
hasAttr,
|
hasAttr,
|
||||||
select,
|
select,
|
||||||
selectWithDefault,
|
selectWithDefault,
|
||||||
|
lookupWith,
|
||||||
validateParams,
|
validateParams,
|
||||||
resolvePath,
|
resolvePath,
|
||||||
coerceToString,
|
coerceToString,
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ import type { NixValue, NixList, NixAttrs, NixString, NixPath } from "./types";
|
|||||||
import { isNixPath } from "./types";
|
import { isNixPath } from "./types";
|
||||||
import { force } from "./thunk";
|
import { force } from "./thunk";
|
||||||
import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert";
|
import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert";
|
||||||
import { getStringValue, getStringContext, mergeContexts, mkStringWithContext } from "./string-context";
|
import {
|
||||||
|
type NixStringContext,
|
||||||
|
getStringValue,
|
||||||
|
getStringContext,
|
||||||
|
mergeContexts,
|
||||||
|
mkStringWithContext,
|
||||||
|
} from "./string-context";
|
||||||
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||||
import { mkPath } from "./path";
|
import { mkPath } from "./path";
|
||||||
import { typeOf, isNixString } from "./builtins/type-check";
|
import { typeOf, isNixString } from "./builtins/type-check";
|
||||||
@@ -146,9 +152,14 @@ export const op = {
|
|||||||
|
|
||||||
// Auto-coerce to string if possible
|
// Auto-coerce to string if possible
|
||||||
if (canCoerceToString(a) && canCoerceToString(b)) {
|
if (canCoerceToString(a) && canCoerceToString(b)) {
|
||||||
const strA = coerceToString(a, StringCoercionMode.Interpolation, false);
|
const context: NixStringContext = new Set();
|
||||||
const strB = coerceToString(b, StringCoercionMode.Interpolation, false);
|
const strA = coerceToString(a, StringCoercionMode.Interpolation, false, context);
|
||||||
return strA + strB;
|
const strB = coerceToString(b, StringCoercionMode.Interpolation, false, context);
|
||||||
|
const result = strA + strB;
|
||||||
|
if (context.size === 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return mkStringWithContext(result, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeric addition
|
// Numeric addition
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class NixThunk implements NixThunkInterface {
|
|||||||
* @param value - Value to check
|
* @param value - Value to check
|
||||||
* @returns true if value is a NixThunk
|
* @returns true if value is a NixThunk
|
||||||
*/
|
*/
|
||||||
export const isThunk = (value: unknown): value is NixThunkInterface => {
|
export const isThunk = (value: NixValue): value is NixThunkInterface => {
|
||||||
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true;
|
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
31
nix-js/runtime-ts/src/types/global.d.ts
vendored
31
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -1,34 +1,5 @@
|
|||||||
import type { NixRuntime } from "..";
|
import type { NixRuntime } from "..";
|
||||||
|
import type { FetchTarballResult, FetchUrlResult, FetchGitResult, FetchHgResult } from "../builtins/io";
|
||||||
interface FetchUrlResult {
|
|
||||||
store_path: string;
|
|
||||||
hash: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FetchTarballResult {
|
|
||||||
store_path: string;
|
|
||||||
hash: string;
|
|
||||||
nar_hash: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FetchGitResult {
|
|
||||||
out_path: string;
|
|
||||||
rev: string;
|
|
||||||
short_rev: string;
|
|
||||||
rev_count: number;
|
|
||||||
last_modified: number;
|
|
||||||
last_modified_date: string;
|
|
||||||
submodules: boolean;
|
|
||||||
nar_hash: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FetchHgResult {
|
|
||||||
out_path: string;
|
|
||||||
branch: string;
|
|
||||||
rev: string;
|
|
||||||
short_rev: string;
|
|
||||||
rev_count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var Nix: NixRuntime;
|
var Nix: NixRuntime;
|
||||||
|
|||||||
@@ -4,6 +4,71 @@ use std::path::Path;
|
|||||||
use crate::ir::*;
|
use crate::ir::*;
|
||||||
use crate::value::Symbol;
|
use crate::value::Symbol;
|
||||||
|
|
||||||
|
macro_rules! code {
|
||||||
|
($buf:expr, $ctx:expr; $($item:expr)*) => {{
|
||||||
|
$(
|
||||||
|
($item).compile($ctx, $buf);
|
||||||
|
)*
|
||||||
|
}};
|
||||||
|
|
||||||
|
($buf:expr, $ctx:expr; $($item:expr)*) => {{
|
||||||
|
$(
|
||||||
|
($item).compile($ctx, $buf);
|
||||||
|
)*
|
||||||
|
}};
|
||||||
|
|
||||||
|
($buf:expr, $fmt:literal, $($arg:tt)*) => {
|
||||||
|
write!($buf, $fmt, $($arg)*).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
($buf:expr, $fmt:literal) => {
|
||||||
|
write!($buf, $fmt).unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
||||||
|
let mut buf = CodeBuffer::with_capacity(8192);
|
||||||
|
|
||||||
|
code!(&mut buf, ctx; "(()=>{");
|
||||||
|
|
||||||
|
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
||||||
|
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
|
||||||
|
}
|
||||||
|
|
||||||
|
code!(&mut buf, ctx;
|
||||||
|
"Nix.builtins.storeDir="
|
||||||
|
quoted(ctx.get_store_dir())
|
||||||
|
";const __currentDir="
|
||||||
|
quoted(&ctx.get_current_dir().display().to_string())
|
||||||
|
";const __with=null;return "
|
||||||
|
expr
|
||||||
|
"})()");
|
||||||
|
|
||||||
|
buf.into_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
||||||
|
let mut buf = CodeBuffer::with_capacity(8192);
|
||||||
|
|
||||||
|
code!(&mut buf, ctx; "((__scope)=>{");
|
||||||
|
|
||||||
|
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
||||||
|
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
|
||||||
|
}
|
||||||
|
|
||||||
|
code!(&mut buf, ctx;
|
||||||
|
"Nix.builtins.storeDir="
|
||||||
|
quoted(ctx.get_store_dir())
|
||||||
|
";const __currentDir="
|
||||||
|
quoted(&ctx.get_current_dir().display().to_string())
|
||||||
|
";return "
|
||||||
|
expr
|
||||||
|
"})"
|
||||||
|
);
|
||||||
|
|
||||||
|
buf.into_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct CodeBuffer {
|
pub(crate) struct CodeBuffer {
|
||||||
buf: String,
|
buf: String,
|
||||||
}
|
}
|
||||||
@@ -83,71 +148,6 @@ fn joined<Ctx: CodegenContext, I: Iterator, F: Fn(&Ctx, &mut CodeBuffer, I::Item
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! code {
|
|
||||||
($buf:expr, $ctx:expr; $($item:expr)*) => {{
|
|
||||||
$(
|
|
||||||
($item).compile($ctx, $buf);
|
|
||||||
)*
|
|
||||||
}};
|
|
||||||
|
|
||||||
($buf:expr, $ctx:expr; $($item:expr)*) => {{
|
|
||||||
$(
|
|
||||||
($item).compile($ctx, $buf);
|
|
||||||
)*
|
|
||||||
}};
|
|
||||||
|
|
||||||
($buf:expr, $fmt:literal, $($arg:tt)*) => {
|
|
||||||
write!($buf, $fmt, $($arg)*).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
($buf:expr, $fmt:literal) => {
|
|
||||||
write!($buf, $fmt).unwrap()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
|
||||||
let mut buf = CodeBuffer::with_capacity(8192);
|
|
||||||
|
|
||||||
code!(&mut buf, ctx; "(()=>{");
|
|
||||||
|
|
||||||
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
|
||||||
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
|
|
||||||
}
|
|
||||||
|
|
||||||
code!(&mut buf, ctx;
|
|
||||||
"Nix.builtins.storeDir="
|
|
||||||
quoted(ctx.get_store_dir())
|
|
||||||
";const __currentDir="
|
|
||||||
quoted(&ctx.get_current_dir().display().to_string())
|
|
||||||
";return "
|
|
||||||
expr
|
|
||||||
"})()");
|
|
||||||
|
|
||||||
buf.into_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
|
||||||
let mut buf = CodeBuffer::with_capacity(8192);
|
|
||||||
|
|
||||||
code!(&mut buf, ctx; "((__scope)=>{");
|
|
||||||
|
|
||||||
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
|
||||||
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
|
|
||||||
}
|
|
||||||
|
|
||||||
code!(&mut buf, ctx;
|
|
||||||
"Nix.builtins.storeDir="
|
|
||||||
quoted(ctx.get_store_dir())
|
|
||||||
";const __currentDir="
|
|
||||||
quoted(&ctx.get_current_dir().display().to_string())
|
|
||||||
";return "
|
|
||||||
expr
|
|
||||||
"})"
|
|
||||||
);
|
|
||||||
|
|
||||||
buf.into_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Compile<Ctx: CodegenContext> {
|
trait Compile<Ctx: CodegenContext> {
|
||||||
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer);
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer);
|
||||||
}
|
}
|
||||||
@@ -318,6 +318,14 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
"]"
|
"]"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Ir::WithExpr(x) => x.compile(ctx, buf),
|
||||||
|
&Ir::WithLookup(WithLookup { inner: name, .. }) => {
|
||||||
|
code!(buf, ctx;
|
||||||
|
"Nix.lookupWith("
|
||||||
|
ctx.get_sym(name)
|
||||||
|
",__with)"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,6 +530,19 @@ impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for WithExpr {
|
||||||
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
let namespace = ctx.get_ir(self.namespace);
|
||||||
|
let body = ctx.get_ir(self.body);
|
||||||
|
let has_thunks = !self.thunks.is_empty();
|
||||||
|
if has_thunks {
|
||||||
|
code!(buf, ctx; "((__with)=>{" self.thunks "return " body "})({env:" namespace ",last:__with})");
|
||||||
|
} else {
|
||||||
|
code!(buf, ctx; "((__with)=>(" body "))({env:" namespace ",last:__with})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
||||||
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
if let Some(default) = self.default {
|
if let Some(default) = self.default {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::downgrade::*;
|
|||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::ir::{
|
use crate::ir::{
|
||||||
Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk,
|
Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk,
|
||||||
ToIr as _,
|
ToIr as _, WithLookup,
|
||||||
};
|
};
|
||||||
use crate::runtime::{Runtime, RuntimeContext};
|
use crate::runtime::{Runtime, RuntimeContext};
|
||||||
use crate::store::{DaemonStore, Store, StoreConfig};
|
use crate::store::{DaemonStore, Store, StoreConfig};
|
||||||
@@ -342,7 +342,6 @@ enum Scope<'ctx> {
|
|||||||
ScopedImport(HashSet<SymId>),
|
ScopedImport(HashSet<SymId>),
|
||||||
Let(HashMap<SymId, ExprId>),
|
Let(HashMap<SymId, ExprId>),
|
||||||
Param(SymId, ExprId),
|
Param(SymId, ExprId),
|
||||||
With(ExprId),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ScopeGuard<'a, 'ctx> {
|
struct ScopeGuard<'a, 'ctx> {
|
||||||
@@ -365,6 +364,7 @@ pub struct DowngradeCtx<'ctx> {
|
|||||||
ctx: &'ctx mut Ctx,
|
ctx: &'ctx mut Ctx,
|
||||||
irs: Vec<Ir>,
|
irs: Vec<Ir>,
|
||||||
scopes: Vec<Scope<'ctx>>,
|
scopes: Vec<Scope<'ctx>>,
|
||||||
|
with_scope_count: usize,
|
||||||
arg_id: usize,
|
arg_id: usize,
|
||||||
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
||||||
}
|
}
|
||||||
@@ -381,6 +381,7 @@ impl<'ctx> DowngradeCtx<'ctx> {
|
|||||||
.collect(),
|
.collect(),
|
||||||
irs: vec![],
|
irs: vec![],
|
||||||
arg_id: 0,
|
arg_id: 0,
|
||||||
|
with_scope_count: 0,
|
||||||
thunk_scopes: vec![Vec::new()],
|
thunk_scopes: vec![Vec::new()],
|
||||||
ctx,
|
ctx,
|
||||||
}
|
}
|
||||||
@@ -472,39 +473,18 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
return Ok(expr);
|
return Ok(expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&Scope::With(_) => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let namespaces: Vec<ExprId> = self
|
if self.with_scope_count > 0 {
|
||||||
.scopes
|
Ok(self.new_expr(WithLookup { inner: sym, span }.to_ir()))
|
||||||
.iter()
|
} else {
|
||||||
.filter_map(|scope| {
|
Err(Error::downgrade_error(
|
||||||
if let &Scope::With(namespace) = scope {
|
|
||||||
Some(namespace)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let mut result = None;
|
|
||||||
for namespace in namespaces {
|
|
||||||
use crate::ir::{Attr, Select};
|
|
||||||
let select = Select {
|
|
||||||
expr: namespace,
|
|
||||||
attrpath: vec![Attr::Str(sym, rnix::TextRange::default())],
|
|
||||||
default: result, // Link to outer With or None
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
result = Some(self.new_expr(select.to_ir()));
|
|
||||||
}
|
|
||||||
result.ok_or_else(|| {
|
|
||||||
Error::downgrade_error(
|
|
||||||
format!("'{}' not found", self.get_sym(sym)),
|
format!("'{}' not found", self.get_sym(sym)),
|
||||||
self.get_current_source(),
|
self.get_current_source(),
|
||||||
span,
|
span,
|
||||||
)
|
))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_ir(&mut self, id: ExprId, expr: Ir) {
|
fn replace_ir(&mut self, id: ExprId, expr: Ir) {
|
||||||
@@ -558,14 +538,14 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
f(guard.as_ctx())
|
f(guard.as_ctx())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_with_scope<F, R>(&mut self, namespace: ExprId, f: F) -> R
|
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(&mut Self) -> R,
|
||||||
{
|
{
|
||||||
let namespace = self.maybe_thunk(namespace);
|
self.with_scope_count += 1;
|
||||||
self.scopes.push(Scope::With(namespace));
|
let ret = f(self);
|
||||||
let mut guard = ScopeGuard { ctx: self };
|
self.with_scope_count -= 1;
|
||||||
f(guard.as_ctx())
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<(ExprId, ExprId)>)
|
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<(ExprId, ExprId)>)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub trait DowngradeContext {
|
|||||||
fn with_let_scope<F, R>(&mut self, bindings: HashMap<SymId, ExprId>, f: F) -> R
|
fn with_let_scope<F, R>(&mut self, bindings: HashMap<SymId, ExprId>, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R;
|
F: FnOnce(&mut Self) -> R;
|
||||||
fn with_with_scope<F, R>(&mut self, namespace: ExprId, f: F) -> R
|
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R;
|
F: FnOnce(&mut Self) -> R;
|
||||||
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<(ExprId, ExprId)>)
|
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<(ExprId, ExprId)>)
|
||||||
@@ -402,13 +402,23 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
|||||||
/// Downgrades a `with` expression.
|
/// Downgrades a `with` expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
// with namespace; expr
|
let span = self.syntax().text_range();
|
||||||
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
||||||
|
let namespace = ctx.maybe_thunk(namespace);
|
||||||
|
|
||||||
// Downgrade body in With scope
|
let (body, thunks) = ctx
|
||||||
let expr = ctx.with_with_scope(namespace, |ctx| self.body().unwrap().downgrade(ctx))?;
|
.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| self.body().unwrap().downgrade(ctx)));
|
||||||
|
let body = body?;
|
||||||
|
|
||||||
Ok(expr)
|
Ok(ctx.new_expr(
|
||||||
|
WithExpr {
|
||||||
|
namespace,
|
||||||
|
body,
|
||||||
|
thunks,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ pub struct FetchUrlResult {
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct FetchTarballResult {
|
pub struct FetchTarballResult {
|
||||||
pub store_path: String,
|
pub store_path: String,
|
||||||
pub hash: String,
|
|
||||||
pub nar_hash: String,
|
pub nar_hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,18 +201,12 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
.get("nar_hash")
|
.get("nar_hash")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
let cached_tarball_hash = cached_entry
|
|
||||||
.info
|
|
||||||
.get("tarball_hash")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
if let Some(ref hex) = expected_hex {
|
if let Some(ref hex) = expected_hex {
|
||||||
if cached_nar_hash == hex {
|
if cached_nar_hash == hex {
|
||||||
info!("Cache hit");
|
info!("Cache hit");
|
||||||
return Ok(FetchTarballResult {
|
return Ok(FetchTarballResult {
|
||||||
store_path: cached_entry.store_path.clone(),
|
store_path: cached_entry.store_path.clone(),
|
||||||
hash: cached_tarball_hash.to_string(),
|
|
||||||
nar_hash: cached_nar_hash.to_string(),
|
nar_hash: cached_nar_hash.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -221,7 +214,6 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
info!("Cache hit (no hash check)");
|
info!("Cache hit (no hash check)");
|
||||||
return Ok(FetchTarballResult {
|
return Ok(FetchTarballResult {
|
||||||
store_path: cached_entry.store_path.clone(),
|
store_path: cached_entry.store_path.clone(),
|
||||||
hash: cached_tarball_hash.to_string(),
|
|
||||||
nar_hash: cached_nar_hash.to_string(),
|
nar_hash: cached_nar_hash.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -235,17 +227,6 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
info!(bytes = data.len(), "Download complete");
|
info!(bytes = data.len(), "Download complete");
|
||||||
|
|
||||||
let tarball_hash = crate::nix_utils::sha256_hex(&String::from_utf8_lossy(&data));
|
|
||||||
|
|
||||||
if let Some(ref expected) = expected_hex
|
|
||||||
&& tarball_hash != *expected
|
|
||||||
{
|
|
||||||
return Err(NixRuntimeError::from(format!(
|
|
||||||
"Tarball hash mismatch for '{}': expected {}, got {}",
|
|
||||||
url, expected, tarball_hash
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Extracting tarball");
|
info!("Extracting tarball");
|
||||||
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
let (extracted_path, _temp_dir) = cache
|
let (extracted_path, _temp_dir) = cache
|
||||||
@@ -258,19 +239,16 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
let nar_hash_hex = hex::encode(nar_hash);
|
let nar_hash_hex = hex::encode(nar_hash);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
tarball_hash = %tarball_hash,
|
|
||||||
nar_hash = %nar_hash_hex,
|
nar_hash = %nar_hash_hex,
|
||||||
"Hash computation complete"
|
"Hash computation complete"
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(ref expected) = expected_sha256
|
if let Some(ref expected) = expected_hex
|
||||||
&& nar_hash != *expected
|
&& nar_hash_hex != *expected
|
||||||
{
|
{
|
||||||
return Err(NixRuntimeError::from(format!(
|
return Err(NixRuntimeError::from(format!(
|
||||||
"NAR hash mismatch for '{}': expected {}, got {}",
|
"Tarball hash mismatch for '{}': expected {}, got {}",
|
||||||
url,
|
url, expected, nar_hash_hex
|
||||||
expected_hex.expect("must be Some"),
|
|
||||||
nar_hash_hex
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,8 +262,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
info!(store_path = %store_path, "Added to store");
|
info!(store_path = %store_path, "Added to store");
|
||||||
|
|
||||||
let info = serde_json::json!({
|
let info = serde_json::json!({
|
||||||
"tarball_hash": tarball_hash,
|
"nar_hash": nar_hash_hex,
|
||||||
"nar_hash": nar_hash,
|
|
||||||
"url": url,
|
"url": url,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,7 +273,6 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
Ok(FetchTarballResult {
|
Ok(FetchTarballResult {
|
||||||
store_path,
|
store_path,
|
||||||
hash: tarball_hash,
|
|
||||||
nar_hash: nar_hash_hex,
|
nar_hash: nar_hash_hex,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -350,43 +326,16 @@ pub fn op_fetch_hg(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_hash(hash: &str) -> String {
|
fn normalize_hash(hash: &str) -> String {
|
||||||
|
use base64::prelude::*;
|
||||||
if hash.starts_with("sha256-")
|
if hash.starts_with("sha256-")
|
||||||
&& let Some(b64) = hash.strip_prefix("sha256-")
|
&& let Some(b64) = hash.strip_prefix("sha256-")
|
||||||
&& let Ok(bytes) = base64_decode(b64)
|
&& let Ok(bytes) = BASE64_STANDARD.decode(b64)
|
||||||
{
|
{
|
||||||
return hex::encode(bytes);
|
return hex::encode(bytes);
|
||||||
}
|
}
|
||||||
hash.to_string()
|
hash.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
|
|
||||||
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
|
|
||||||
let input = input.trim_end_matches('=');
|
|
||||||
let mut output = Vec::with_capacity(input.len() * 3 / 4);
|
|
||||||
|
|
||||||
let mut buffer = 0u32;
|
|
||||||
let mut bits = 0;
|
|
||||||
|
|
||||||
for c in input.bytes() {
|
|
||||||
let value = ALPHABET
|
|
||||||
.iter()
|
|
||||||
.position(|&x| x == c)
|
|
||||||
.ok_or_else(|| format!("Invalid base64 character: {}", c as char))?;
|
|
||||||
|
|
||||||
buffer = (buffer << 6) | (value as u32);
|
|
||||||
bits += 6;
|
|
||||||
|
|
||||||
if bits >= 8 {
|
|
||||||
bits -= 8;
|
|
||||||
output.push((buffer >> bits) as u8);
|
|
||||||
buffer &= (1 << bits) - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_ops<Ctx: RuntimeContext>() -> Vec<deno_core::OpDecl> {
|
pub fn register_ops<Ctx: RuntimeContext>() -> Vec<deno_core::OpDecl> {
|
||||||
vec![
|
vec![
|
||||||
op_fetch_url::<Ctx>(),
|
op_fetch_url::<Ctx>(),
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ ir! {
|
|||||||
CurPos,
|
CurPos,
|
||||||
ReplBinding(SymId),
|
ReplBinding(SymId),
|
||||||
ScopedImportBinding(SymId),
|
ScopedImportBinding(SymId),
|
||||||
|
WithExpr { pub namespace: ExprId, pub body: ExprId, pub thunks: Vec<(ExprId, ExprId)> },
|
||||||
|
WithLookup(SymId),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
|||||||
@@ -3,6 +3,42 @@ mod utils;
|
|||||||
use nix_js::value::Value;
|
use nix_js::value::Value;
|
||||||
use utils::{eval_deep, eval_deep_result};
|
use utils::{eval_deep, eval_deep_result};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_operator_preserves_derivation_context() {
|
||||||
|
let result = eval_deep(
|
||||||
|
r#"
|
||||||
|
let
|
||||||
|
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; outputs = ["out" "dev"]; };
|
||||||
|
getOutput = output: pkg: pkg.${output} or pkg.out or pkg;
|
||||||
|
user = derivation {
|
||||||
|
name = "user";
|
||||||
|
builder = "/bin/sh";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
libPath = (getOutput "lib" dep) + "/lib";
|
||||||
|
devPath = dep.dev + "/include";
|
||||||
|
};
|
||||||
|
in user.drvPath
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let nix_result = eval_deep(
|
||||||
|
r#"
|
||||||
|
let
|
||||||
|
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; outputs = ["out" "dev"]; };
|
||||||
|
user = derivation {
|
||||||
|
name = "user";
|
||||||
|
builder = "/bin/sh";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
libPath = "${dep.out}/lib";
|
||||||
|
devPath = "${dep.dev}/include";
|
||||||
|
};
|
||||||
|
in user.drvPath
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(result, nix_result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn derivation_minimal() {
|
fn derivation_minimal() {
|
||||||
let result = eval_deep(
|
let result = eval_deep(
|
||||||
@@ -505,7 +541,7 @@ fn structured_attrs_rejects_functions() {
|
|||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
let err_msg = result.unwrap_err().to_string();
|
let err_msg = result.unwrap_err().to_string();
|
||||||
assert!(err_msg.contains("function") && err_msg.contains("serialize"));
|
assert!(err_msg.contains("cannot convert lambda to JSON"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user