diff --git a/nix-js/runtime-ts/src/builtins/conversion.ts b/nix-js/runtime-ts/src/builtins/conversion.ts index 8a33883..b287faf 100644 --- a/nix-js/runtime-ts/src/builtins/conversion.ts +++ b/nix-js/runtime-ts/src/builtins/conversion.ts @@ -5,21 +5,21 @@ import type { NixValue } from "../types"; export const fromJSON = (e: NixValue): never => { - throw "Not implemented: fromJSON"; + throw new Error("Not implemented: fromJSON"); }; export const fromTOML = (e: NixValue): never => { - throw "Not implemented: fromTOML"; + throw new Error("Not implemented: fromTOML"); }; export const toJSON = (e: NixValue): never => { - throw "Not implemented: toJSON"; + throw new Error("Not implemented: toJSON"); }; export const toXML = (e: NixValue): never => { - throw "Not implemented: toXML"; + throw new Error("Not implemented: toXML"); }; export const toString = (name: NixValue, s: NixValue): never => { - throw "Not implemented: toString"; + throw new Error("Not implemented: toString"); }; diff --git a/nix-js/runtime-ts/src/builtins/derivation.ts b/nix-js/runtime-ts/src/builtins/derivation.ts index 34fc186..f00a9be 100644 --- a/nix-js/runtime-ts/src/builtins/derivation.ts +++ b/nix-js/runtime-ts/src/builtins/derivation.ts @@ -1,9 +1,9 @@ import type { NixValue } from "../types"; export const derivation = (args: NixValue) => { - throw "Not implemented: derivation"; + throw new Error("Not implemented: derivation"); }; export const derivationStrict = (args: NixValue) => { - throw "Not implemented: derivationStrict"; + throw new Error("Not implemented: derivationStrict"); }; diff --git a/nix-js/runtime-ts/src/builtins/functional.ts b/nix-js/runtime-ts/src/builtins/functional.ts index aeadcdb..64e7c6e 100644 --- a/nix-js/runtime-ts/src/builtins/functional.ts +++ b/nix-js/runtime-ts/src/builtins/functional.ts @@ -2,8 +2,9 @@ * Functional programming builtin functions */ -import type { NixValue } from "../types"; +import { CatchableError, type NixValue } from "../types"; import { force } from "../thunk"; +import { force_string } from "../type-assert"; export const seq = (e1: NixValue) => @@ -15,15 +16,15 @@ export const seq = export const deepSeq = (e1: NixValue) => (e2: NixValue): never => { - throw "Not implemented: deepSeq"; + throw new Error("Not implemented: deepSeq"); }; export const abort = (s: NixValue): never => { - throw `evaluation aborted with the following error message: '${force(s)}'`; + throw new Error(`evaluation aborted with the following error message: '${force(s)}'`); }; export const throwFunc = (s: NixValue): never => { - throw force(s); + throw new CatchableError(force_string(s)); }; export const trace = (e1: NixValue, e2: NixValue): NixValue => { diff --git a/nix-js/runtime-ts/src/builtins/index.ts b/nix-js/runtime-ts/src/builtins/index.ts index 9ffe6dd..aa4867e 100644 --- a/nix-js/runtime-ts/src/builtins/index.ts +++ b/nix-js/runtime-ts/src/builtins/index.ts @@ -249,7 +249,7 @@ export const builtins: any = { builtins: create_thunk(() => builtins), currentSystem: create_thunk(() => { - throw "Not implemented: currentSystem"; + throw new Error("Not implemented: currentSystem"); }), currentTime: create_thunk(() => Date.now()), diff --git a/nix-js/runtime-ts/src/builtins/io.ts b/nix-js/runtime-ts/src/builtins/io.ts index 983ca4a..a20ebb1 100644 --- a/nix-js/runtime-ts/src/builtins/io.ts +++ b/nix-js/runtime-ts/src/builtins/io.ts @@ -25,39 +25,39 @@ export const importFunc = (path: NixValue): NixValue => { export const scopedImport = (scope: NixValue) => (path: NixValue): never => { - throw "Not implemented: scopedImport"; + throw new Error("Not implemented: scopedImport"); }; export const storePath = (args: NixValue): never => { - throw "Not implemented: storePath"; + throw new Error("Not implemented: storePath"); }; export const fetchClosure = (args: NixValue): never => { - throw "Not implemented: fetchClosure"; + throw new Error("Not implemented: fetchClosure"); }; export const fetchMercurial = (args: NixValue): never => { - throw "Not implemented: fetchMercurial"; + throw new Error("Not implemented: fetchMercurial"); }; export const fetchGit = (args: NixValue): never => { - throw "Not implemented: fetchGit"; + throw new Error("Not implemented: fetchGit"); }; export const fetchTarball = (args: NixValue): never => { - throw "Not implemented: fetchTarball"; + throw new Error("Not implemented: fetchTarball"); }; export const fetchTree = (args: NixValue): never => { - throw "Not implemented: fetchTree"; + throw new Error("Not implemented: fetchTree"); }; export const fetchurl = (args: NixValue): never => { - throw "Not implemented: fetchurl"; + throw new Error("Not implemented: fetchurl"); }; export const readDir = (path: NixValue): never => { - throw "Not implemented: readDir"; + throw new Error("Not implemented: readDir"); }; export const readFile = (path: NixValue): string => { @@ -66,7 +66,7 @@ export const readFile = (path: NixValue): string => { }; export const readFileType = (path: NixValue): never => { - throw "Not implemented: readFileType"; + throw new Error("Not implemented: readFileType"); }; export const pathExists = (path: NixValue): boolean => { @@ -75,27 +75,27 @@ export const pathExists = (path: NixValue): boolean => { }; export const path = (args: NixValue): never => { - throw "Not implemented: path"; + throw new Error("Not implemented: path"); }; export const toFile = (name: NixValue, s: NixValue): never => { - throw "Not implemented: toFile"; + throw new Error("Not implemented: toFile"); }; export const toPath = (name: NixValue, s: NixValue): never => { - throw "Not implemented: toPath"; + throw new Error("Not implemented: toPath"); }; export const filterSource = (args: NixValue): never => { - throw "Not implemented: filterSource"; + throw new Error("Not implemented: filterSource"); }; export const findFile = (search: NixValue) => (lookup: NixValue): never => { - throw "Not implemented: findFile"; + throw new Error("Not implemented: findFile"); }; export const getEnv = (s: NixValue): never => { - throw "Not implemented: getEnv"; + throw new Error("Not implemented: getEnv"); }; diff --git a/nix-js/runtime-ts/src/builtins/misc.ts b/nix-js/runtime-ts/src/builtins/misc.ts index 18d752d..e2cab1f 100644 --- a/nix-js/runtime-ts/src/builtins/misc.ts +++ b/nix-js/runtime-ts/src/builtins/misc.ts @@ -2,141 +2,155 @@ * Miscellaneous unimplemented builtin functions */ -import type { NixValue } from "../types"; +import { force } from "../thunk"; +import { CatchableError } from "../types"; +import type { NixBool, NixStrictValue, NixValue } from "../types"; export const addErrorContext = (e1: NixValue) => (e2: NixValue): never => { - throw "Not implemented: addErrorContext"; + throw new Error("Not implemented: addErrorContext"); }; export const appendContext = (e1: NixValue) => (e2: NixValue): never => { - throw "Not implemented: appendContext"; + throw new Error("Not implemented: appendContext"); }; export const getContext = (s: NixValue): never => { - throw "Not implemented: getContext"; + throw new Error("Not implemented: getContext"); }; export const hasContext = (s: NixValue): never => { - throw "Not implemented: hasContext"; + throw new Error("Not implemented: hasContext"); }; export const hashFile = (type: NixValue) => (p: NixValue): never => { - throw "Not implemented: hashFile"; + throw new Error("Not implemented: hashFile"); }; export const hashString = (type: NixValue) => (p: NixValue): never => { - throw "Not implemented: hashString"; + throw new Error("Not implemented: hashString"); }; export const convertHash = (args: NixValue): never => { - throw "Not implemented: convertHash"; + throw new Error("Not implemented: convertHash"); }; export const unsafeDiscardOutputDependency = (s: NixValue): never => { - throw "Not implemented: unsafeDiscardOutputDependency"; + throw new Error("Not implemented: unsafeDiscardOutputDependency"); }; export const unsafeDiscardStringContext = (s: NixValue): never => { - throw "Not implemented: unsafeDiscardStringContext"; + throw new Error("Not implemented: unsafeDiscardStringContext"); }; export const unsafeGetAttrPos = (s: NixValue): never => { - throw "Not implemented: unsafeGetAttrPos"; + throw new Error("Not implemented: unsafeGetAttrPos"); }; export const addDrvOutputDependencies = (s: NixValue): never => { - throw "Not implemented: addDrvOutputDependencies"; + throw new Error("Not implemented: addDrvOutputDependencies"); }; export const compareVersions = (s1: NixValue) => (s2: NixValue): never => { - throw "Not implemented: compareVersions"; + throw new Error("Not implemented: compareVersions"); }; export const dirOf = (s: NixValue): never => { - throw "Not implemented: dirOf"; + throw new Error("Not implemented: dirOf"); }; export const flakeRefToString = (attrs: NixValue): never => { - throw "Not implemented: flakeRefToString"; + throw new Error("Not implemented: flakeRefToString"); }; export const functionArgs = (f: NixValue): never => { - throw "Not implemented: functionArgs"; + throw new Error("Not implemented: functionArgs"); }; export const genericClosure = (args: NixValue): never => { - throw "Not implemented: genericClosure"; + throw new Error("Not implemented: genericClosure"); }; export const getFlake = (attrs: NixValue): never => { - throw "Not implemented: getFlake"; + throw new Error("Not implemented: getFlake"); }; export const match = (regex: NixValue) => (str: NixValue): never => { - throw "Not implemented: match"; + throw new Error("Not implemented: match"); }; export const outputOf = (drv: NixValue) => (out: NixValue): never => { - throw "Not implemented: outputOf"; + throw new Error("Not implemented: outputOf"); }; export const parseDrvName = (s: NixValue): never => { - throw "Not implemented: parseDrvName"; + throw new Error("Not implemented: parseDrvName"); }; export const parseFlakeName = (s: NixValue): never => { - throw "Not implemented: parseFlakeName"; + throw new Error("Not implemented: parseFlakeName"); }; export const parseFlakeRef = (s: NixValue): never => { - throw "Not implemented: parseFlakeRef"; + throw new Error("Not implemented: parseFlakeRef"); }; export const placeholder = (output: NixValue): never => { - throw "Not implemented: placeholder"; + throw new Error("Not implemented: placeholder"); }; export const replaceStrings = (from: NixValue) => (to: NixValue) => (s: NixValue): never => { - throw "Not implemented: replaceStrings"; + throw new Error("Not implemented: replaceStrings"); }; export const split = (regex: NixValue, str: NixValue): never => { - throw "Not implemented: split"; + throw new Error("Not implemented: split"); }; export const splitVersion = (s: NixValue): never => { - throw "Not implemented: splitVersion"; + throw new Error("Not implemented: splitVersion"); }; export const traceVerbose = (e1: NixValue, e2: NixValue): never => { - throw "Not implemented: traceVerbose"; + throw new Error("Not implemented: traceVerbose"); }; -export const tryEval = - (e1: NixValue) => - (e2: NixValue): never => { - throw "Not implemented: tryEval"; - }; +export const tryEval = (e: NixValue): { success: NixBool; value: NixStrictValue } => { + try { + return { + success: true, + value: force(e), + }; + } catch (err) { + if (err instanceof CatchableError) { + return { + success: false, + value: false, + }; + } else { + throw err; + } + } +}; export const zipAttrsWith = (f: NixValue) => (list: NixValue): never => { - throw "Not implemented: zipAttrsWith"; + throw new Error("Not implemented: zipAttrsWith"); }; diff --git a/nix-js/runtime-ts/src/builtins/type-check.ts b/nix-js/runtime-ts/src/builtins/type-check.ts index 943e97c..42c6b85 100644 --- a/nix-js/runtime-ts/src/builtins/type-check.ts +++ b/nix-js/runtime-ts/src/builtins/type-check.ts @@ -10,6 +10,7 @@ import type { NixInt, NixList, NixNull, + NixStrictValue, NixString, NixValue, } from "../types"; @@ -39,7 +40,7 @@ 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"; + throw new Error("Not implemented: isPath"); }; export const isString = (e: NixValue): e is NixString => typeof force(e) === "string"; diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index abc42cc..545ddd4 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -60,7 +60,7 @@ export const select_with_default = (obj: NixValue, key: NixValue, default_val: N export const has_attr = (obj: NixValue, attrpath: NixValue[]): NixBool => { if (!isAttrs(obj)) { - return false + return false; } let attrs = obj; diff --git a/nix-js/runtime-ts/src/thunk.ts b/nix-js/runtime-ts/src/thunk.ts index 7816b5c..488f904 100644 --- a/nix-js/runtime-ts/src/thunk.ts +++ b/nix-js/runtime-ts/src/thunk.ts @@ -3,7 +3,7 @@ * Implements thunks for lazy evaluation of Nix expressions */ -import type { NixValue, NixThunkInterface } from "./types"; +import type { NixValue, NixThunkInterface, NixStrictValue } from "./types"; /** * Symbol used to mark objects as thunks @@ -20,12 +20,12 @@ export const IS_THUNK = Symbol("is_thunk"); export class NixThunk implements NixThunkInterface { [key: symbol]: any; readonly [IS_THUNK] = true as const; - func: (() => NixValue) | null; - result: Exclude | null; + func: (() => NixValue) | undefined; + result: NixStrictValue | undefined; constructor(func: () => NixValue) { this.func = func; - this.result = null; + this.result = undefined; } } @@ -46,20 +46,20 @@ export const is_thunk = (value: unknown): value is NixThunkInterface => { * @param value - Value to force (may be a thunk) * @returns The forced/evaluated value */ -export const force = (value: NixValue): Exclude => { +export const force = (value: NixValue): NixStrictValue => { if (!is_thunk(value)) { return value; } // Already evaluated - return cached result - if (value.func === null) { + if (value.func === undefined) { return value.result!; } // Evaluate and cache const result = force(value.func()); value.result = result; - value.func = null; + value.func = undefined; return result; }; diff --git a/nix-js/runtime-ts/src/types.ts b/nix-js/runtime-ts/src/types.ts index e4644a3..8c1f039 100644 --- a/nix-js/runtime-ts/src/types.ts +++ b/nix-js/runtime-ts/src/types.ts @@ -2,6 +2,8 @@ * Core TypeScript type definitions for nix-js runtime */ +import { IS_THUNK } from "./thunk"; + // Nix primitive types export type NixInt = bigint; export type NixFloat = number; @@ -20,9 +22,9 @@ export type NixFunction = (...args: any[]) => any; * Thunks delay evaluation until forced */ export interface NixThunkInterface { - readonly [key: symbol]: true; // IS_THUNK marker - func: (() => NixValue) | null; - result: Exclude | null; + readonly [IS_THUNK]: true; + func: (() => NixValue) | undefined; + result: NixStrictValue | undefined; } // Union of all Nix primitive types @@ -34,6 +36,18 @@ export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString; */ export type NixValue = NixPrimitive | NixList | NixAttrs | NixFunction | NixThunkInterface; +export type NixStrictValue = Exclude; + +/** + * CatchableError: Error type thrown by `builtins.throw` + * This can be caught by `builtins.tryEval` + */ +export class CatchableError extends Error { + constructor(msg: string) { + super(msg); + } +} + // Operator function signatures export type BinaryOp = (a: T, b: U) => R; export type UnaryOp = (a: T) => R; diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index 83b6a35..7cf1cad 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -3,6 +3,7 @@ use std::pin::Pin; use std::sync::Once; use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8}; +use deno_error::JsErrorClass; use crate::codegen::{CodegenContext, Compile}; use crate::context::{CtxPtr, PathDropGuard}; @@ -44,12 +45,12 @@ mod private { pub struct SimpleErrorWrapper(pub(crate) String); impl std::fmt::Display for SimpleErrorWrapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) + std::fmt::Display::fmt(&self.0, f) } } impl std::error::Error for SimpleErrorWrapper {} - js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError"); + js_error_wrapper!(SimpleErrorWrapper, NixError, "Error"); impl From for NixError { fn from(value: String) -> Self { @@ -71,10 +72,13 @@ fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result let ctx = unsafe { ptr.as_mut() }; let current_dir = ctx.get_current_dir(); - let absolute_path = current_dir + let mut absolute_path = current_dir .join(&path) .canonicalize() .map_err(|e| format!("Failed to resolve path {}: {}", path, e))?; + if absolute_path.is_dir() { + absolute_path.push("default.nix") + } let mut guard = PathDropGuard::new(absolute_path.clone(), ctx); let ctx = guard.as_ctx(); @@ -175,7 +179,7 @@ impl Runtime { let global_value = self .js_runtime .execute_script("", script) - .map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?; + .map_err(|e| Error::eval_error(format!("{}", e.get_message())))?; // Retrieve scope from JsRuntime deno_core::scope!(scope, self.js_runtime);