diff --git a/nixjs-rt/src/builtins.ts b/nixjs-rt/src/builtins.ts index 1f9dd03..9725911 100644 --- a/nixjs-rt/src/builtins.ts +++ b/nixjs-rt/src/builtins.ts @@ -38,30 +38,19 @@ function builtinBasicTypeMismatchError( } export function getBuiltins() { + // Builtins are sorted by the order they appear in the Nix manual + // https://nixos.org/manual/nix/stable/language/builtins.html + const builtins: BuiltinsRecord = { - abort: (message) => { - throw abortError(message.asString()); + derivation: (arg) => { + throw new Error("unimplemented"); }, - import: (path) => { - const pathStrict = path.toStrict(); - - if (!(pathStrict instanceof Path || pathStrict instanceof NixString)) { - const expected = [Path, NixString]; - throw builtinBasicTypeMismatchError("import", pathStrict, expected); - } - - const pathValue = pathStrict.toJs(); - - // Below is an intrinsic function that's injected by the Nix evaluator. - // @ts-ignore - const resultingFn: (ctx: EvalCtx) => NixType = importNixModule(pathValue); - - const ctx = new EvalCtx(pathValue); - return resultingFn(ctx); + abort: (message) => { + throw abortError(message.asString()); }, - add: (lhs): Lambda => { + add: (lhs) => { return new Lambda((rhs) => { let lhsStrict = lhs.toStrict(); if (!(lhsStrict instanceof NixInt || lhsStrict instanceof NixFloat)) { @@ -77,19 +66,8 @@ export function getBuiltins() { }); }, - head: (list) => { - const listStrict = list.toStrict(); - if (!(listStrict instanceof NixList)) { - throw typeMismatchError( - listStrict, - NixList, - err`Cannot apply the 'head' function on '${errType(listStrict)}', expected ${errType(NixList)}.`, - ); - } - if (listStrict.values.length === 0) { - throw otherError("Cannot fetch the first element in an empty list."); - } - return listStrict.values[0]; + addDrvOutputDependencies: (arg) => { + throw new Error("unimplemented"); }, all: (pred) => { @@ -171,6 +149,420 @@ export function getBuiltins() { keys.map((key) => attrset.select([new NixString(key)], NULL)), ); }, + + baseNameOf: (path) => { + // Can take a string or path + const pathStrict = path.toStrict(); + if (!(pathStrict instanceof Path || pathStrict instanceof NixString)) { + const expected = [Path, NixString]; + throw builtinBasicTypeMismatchError("baseNameOf", pathStrict, expected); + } + + let pathValue = pathStrict.toJs(); + if (pathValue.endsWith("/")) { + pathValue = pathValue.slice(0, -1); + } + + const parts = pathValue.split("/"); + return new NixString(parts[parts.length - 1]); + }, + + bitAnd: (arg) => { + throw new Error("unimplemented"); + }, + + bitOr: (arg) => { + throw new Error("unimplemented"); + }, + + bitXor: (arg) => { + throw new Error("unimplemented"); + }, + + break: (arg) => { + throw new Error("unimplemented"); + }, + + catAttrs: (arg) => { + throw new Error("unimplemented"); + }, + + ceil: (arg) => { + throw new Error("unimplemented"); + }, + + compareVersions: (arg) => { + throw new Error("unimplemented"); + }, + + concatLists: (arg) => { + throw new Error("unimplemented"); + }, + + concatMap: (arg) => { + throw new Error("unimplemented"); + }, + + concatStringsSep: (arg) => { + throw new Error("unimplemented"); + }, + + convertHash: (arg) => { + throw new Error("unimplemented"); + }, + + deepSeq: (arg) => { + throw new Error("unimplemented"); + }, + + dirOf: (arg) => { + throw new Error("unimplemented"); + }, + + div: (arg) => { + throw new Error("unimplemented"); + }, + + elem: (arg) => { + throw new Error("unimplemented"); + }, + + elemAt: (arg) => { + throw new Error("unimplemented"); + }, + + fetchClosure: (arg) => { + throw new Error("unimplemented"); + }, + + fetchGit: (arg) => { + throw new Error("unimplemented"); + }, + + fetchTarball: (arg) => { + throw new Error("unimplemented"); + }, + + fetchTree: (arg) => { + throw new Error("unimplemented"); + }, + + fetchurl: (arg) => { + throw new Error("unimplemented"); + }, + + filter: (arg) => { + throw new Error("unimplemented"); + }, + + filterSource: (arg) => { + throw new Error("unimplemented"); + }, + + findFile: (arg) => { + throw new Error("unimplemented"); + }, + + flakeRefToString: (arg) => { + throw new Error("unimplemented"); + }, + + floor: (arg) => { + throw new Error("unimplemented"); + }, + + foldl: (arg) => { + throw new Error("unimplemented"); + }, + + fromJSON: (arg) => { + throw new Error("unimplemented"); + }, + + fromTOML: (arg) => { + throw new Error("unimplemented"); + }, + + functionArgs: (arg) => { + throw new Error("unimplemented"); + }, + + genList: (arg) => { + throw new Error("unimplemented"); + }, + + genericClosure: (arg) => { + throw new Error("unimplemented"); + }, + + getAttr: (arg) => { + throw new Error("unimplemented"); + }, + + getContext: (arg) => { + throw new Error("unimplemented"); + }, + + getEnv: (arg) => { + throw new Error("unimplemented"); + }, + + getFlake: (arg) => { + throw new Error("unimplemented"); + }, + + groupBy: (arg) => { + throw new Error("unimplemented"); + }, + + hasAttr: (arg) => { + throw new Error("unimplemented"); + }, + + hasContext: (arg) => { + throw new Error("unimplemented"); + }, + + hashFile: (arg) => { + throw new Error("unimplemented"); + }, + + hashString: (arg) => { + throw new Error("unimplemented"); + }, + + head: (list) => { + const listStrict = list.toStrict(); + if (!(listStrict instanceof NixList)) { + throw typeMismatchError( + listStrict, + NixList, + err`Cannot apply the 'head' function on '${errType(listStrict)}', expected ${errType(NixList)}.`, + ); + } + if (listStrict.values.length === 0) { + throw otherError("Cannot fetch the first element in an empty list."); + } + return listStrict.values[0]; + }, + + import: (path) => { + const pathStrict = path.toStrict(); + + if (!(pathStrict instanceof Path || pathStrict instanceof NixString)) { + const expected = [Path, NixString]; + throw builtinBasicTypeMismatchError("import", pathStrict, expected); + } + + const pathValue = pathStrict.toJs(); + + // Below is an intrinsic function that's injected by the Nix evaluator. + // @ts-ignore + const resultingFn: (ctx: EvalCtx) => NixType = importNixModule(pathValue); + + const ctx = new EvalCtx(pathValue); + return resultingFn(ctx); + }, + + intersectAttrs: (arg) => { + throw new Error("unimplemented"); + }, + + isAttrs: (arg) => { + throw new Error("unimplemented"); + }, + + isBool: (arg) => { + throw new Error("unimplemented"); + }, + + isFloat: (arg) => { + throw new Error("unimplemented"); + }, + + isFunction: (arg) => { + throw new Error("unimplemented"); + }, + + isInt: (arg) => { + throw new Error("unimplemented"); + }, + + isList: (arg) => { + throw new Error("unimplemented"); + }, + + isNull: (arg) => { + throw new Error("unimplemented"); + }, + + isPath: (arg) => { + throw new Error("unimplemented"); + }, + + isString: (arg) => { + throw new Error("unimplemented"); + }, + + length: (arg) => { + throw new Error("unimplemented"); + }, + + lessThan: (arg) => { + throw new Error("unimplemented"); + }, + + listToAttrs: (arg) => { + throw new Error("unimplemented"); + }, + + map: (arg) => { + throw new Error("unimplemented"); + }, + + mapAttrs: (arg) => { + throw new Error("unimplemented"); + }, + + match: (arg) => { + throw new Error("unimplemented"); + }, + + mul: (arg) => { + throw new Error("unimplemented"); + }, + + outputOf: (arg) => { + throw new Error("unimplemented"); + }, + + parseDrvName: (arg) => { + throw new Error("unimplemented"); + }, + + parseFlakeRef: (arg) => { + throw new Error("unimplemented"); + }, + + partition: (arg) => { + throw new Error("unimplemented"); + }, + + path: (arg) => { + throw new Error("unimplemented"); + }, + + pathExists: (arg) => { + throw new Error("unimplemented"); + }, + + placeholder: (arg) => { + throw new Error("unimplemented"); + }, + + readDir: (arg) => { + throw new Error("unimplemented"); + }, + + readFile: (arg) => { + throw new Error("unimplemented"); + }, + + readFileType: (arg) => { + throw new Error("unimplemented"); + }, + + removeAttrs: (arg) => { + throw new Error("unimplemented"); + }, + + replaceStrings: (arg) => { + throw new Error("unimplemented"); + }, + + seq: (arg) => { + throw new Error("unimplemented"); + }, + + sort: (arg) => { + throw new Error("unimplemented"); + }, + + split: (arg) => { + throw new Error("unimplemented"); + }, + + splitVersion: (arg) => { + throw new Error("unimplemented"); + }, + + storePath: (arg) => { + throw new Error("unimplemented"); + }, + + stringLength: (arg) => { + throw new Error("unimplemented"); + }, + + sub: (arg) => { + throw new Error("unimplemented"); + }, + + substring: (arg) => { + throw new Error("unimplemented"); + }, + + tail: (arg) => { + throw new Error("unimplemented"); + }, + + throw: (arg) => { + throw new Error("unimplemented"); + }, + + toFile: (arg) => { + throw new Error("unimplemented"); + }, + + toJSON: (arg) => { + throw new Error("unimplemented"); + }, + + toPath: (arg) => { + throw new Error("unimplemented"); + }, + + toString: (arg) => { + throw new Error("unimplemented"); + }, + + toXML: (arg) => { + throw new Error("unimplemented"); + }, + + trace: (arg) => { + throw new Error("unimplemented"); + }, + + traceVerbose: (arg) => { + throw new Error("unimplemented"); + }, + + tryEval: (arg) => { + throw new Error("unimplemented"); + }, + + typeOf: (arg) => { + throw new Error("unimplemented"); + }, + + unsafeDiscardOutputDependency: (arg) => { + throw new Error("unimplemented"); + }, + + zipAttrsWith: (arg) => { + throw new Error("unimplemented"); + }, }; return builtins; diff --git a/src/tests/builtins.rs b/src/tests/builtins.rs index 99722e9..f9240bb 100644 --- a/src/tests/builtins.rs +++ b/src/tests/builtins.rs @@ -1,11 +1,20 @@ +#![allow(unused_imports)] +#![allow(non_snake_case)] + use crate::{eval::error::NixErrorKind, tests::eval_err}; use crate::{ eval::types::{NixTypeKind, Value}, tests::eval_ok, }; -mod abort { +// Builtins are sorted by the order they appear in the Nix manual +// https://nixos.org/manual/nix/stable/language/builtins.html + +mod derivation { + use super::*; +} +mod abort { use super::*; #[test] @@ -39,29 +48,8 @@ mod add { } } -mod head { +mod addDrvOutputDependencies { use super::*; - - #[test] - fn eval() { - assert_eq!(eval_ok("builtins.head [ 1 2 ]"), Value::Int(1)); - } - - #[test] - fn eval_lazy() { - assert_eq!(eval_ok("builtins.head [ 1 (1 / 0) ]"), Value::Int(1)); - } - - #[test] - fn eval_empty() { - // Would be weird to have a custom error message kind for this, imo. - assert_eq!( - eval_err("builtins.head []"), - NixErrorKind::Other { - message: "Cannot fetch the first element in an empty list.".to_string() - } - ); - } } mod all { @@ -166,7 +154,7 @@ mod any { } } -mod attr_names { +mod attrNames { use super::*; #[test] @@ -210,7 +198,7 @@ mod attr_names { } } -mod attr_values { +mod attrValues { use super::*; #[test] @@ -256,6 +244,244 @@ mod attr_values { } } +mod baseNameOf { + use super::*; + + // Returns everything following the final slash + + #[test] + fn eval() { + assert_eq!( + eval_ok("builtins.baseNameOf \"/foo/bar/baz\""), + Value::Str("baz".into()) + ); + assert_eq!( + eval_ok("builtins.baseNameOf \"/foo/bar/baz/\""), + Value::Str("baz".into()) + ); + assert_eq!( + eval_ok("builtins.baseNameOf \"/foo/bar/baz//\""), + Value::Str("".into()) + ); + assert_eq!( + eval_ok("builtins.baseNameOf \"foo\""), + Value::Str("foo".into()) + ); + } + + #[test] + fn eval_path() { + assert_eq!( + eval_ok("builtins.baseNameOf /foo/bar/baz"), + Value::Str("baz".into()) + ); + assert_eq!( + eval_ok("builtins.baseNameOf ./foo"), + Value::Str("foo".into()) + ); + } + + #[test] + fn eval_invalid_types() { + assert_eq!( + eval_err("builtins.baseNameOf 1"), + NixErrorKind::TypeMismatch { + expected: vec![NixTypeKind::String, NixTypeKind::Path], + got: NixTypeKind::Int + } + ); + } +} + +mod bitAnd { + use super::*; +} + +mod bitOr { + use super::*; +} + +mod bitXor { + use super::*; +} + +mod break_ { + use super::*; +} + +mod catAttrs { + use super::*; +} + +mod ceil { + use super::*; +} + +mod compareVersions { + use super::*; +} + +mod concatLists { + use super::*; +} + +mod concatMap { + use super::*; +} + +mod concatStringsSep { + use super::*; +} + +mod convertHash { + use super::*; +} + +mod deepSeq { + use super::*; +} + +mod dirOf { + use super::*; +} + +mod div { + use super::*; +} + +mod elem { + use super::*; +} + +mod elemAt { + use super::*; +} + +mod fetchClosure { + use super::*; +} + +mod fetchGit { + use super::*; +} + +mod fetchTarball { + use super::*; +} + +mod fetchTree { + use super::*; +} + +mod fetchurl { + use super::*; +} + +mod filter { + use super::*; +} + +mod filterSource { + use super::*; +} + +mod findFile { + use super::*; +} + +mod flakeRefToString { + use super::*; +} + +mod floor { + use super::*; +} + +mod foldl { + use super::*; +} + +mod fromJSON { + use super::*; +} + +mod fromTOML { + use super::*; +} + +mod functionArgs { + use super::*; +} + +mod genList { + use super::*; +} + +mod genericClosure { + use super::*; +} + +mod getAttr { + use super::*; +} + +mod getContext { + use super::*; +} + +mod getEnv { + use super::*; +} + +mod getFlake { + use super::*; +} + +mod groupBy { + use super::*; +} + +mod hasAttr { + use super::*; +} + +mod hasContext { + use super::*; +} + +mod hashFile { + use super::*; +} + +mod hashString { + use super::*; +} + +mod head { + use super::*; + + #[test] + fn eval() { + assert_eq!(eval_ok("builtins.head [ 1 2 ]"), Value::Int(1)); + } + + #[test] + fn eval_lazy() { + assert_eq!(eval_ok("builtins.head [ 1 (1 / 0) ]"), Value::Int(1)); + } + + #[test] + fn eval_empty() { + // Would be weird to have a custom error message kind for this, imo. + assert_eq!( + eval_err("builtins.head []"), + NixErrorKind::Other { + message: "Cannot fetch the first element in an empty list.".to_string() + } + ); + } +} + mod import { use super::*; @@ -285,3 +511,203 @@ mod import { // ); // } } + +mod intersectAttrs { + use super::*; +} + +mod isAttrs { + use super::*; +} + +mod isBool { + use super::*; +} + +mod isFloat { + use super::*; +} + +mod isFunction { + use super::*; +} + +mod isInt { + use super::*; +} + +mod isList { + use super::*; +} + +mod isNull { + use super::*; +} + +mod isPath { + use super::*; +} + +mod isString { + use super::*; +} + +mod length { + use super::*; +} + +mod lessThan { + use super::*; +} + +mod listToAttrs { + use super::*; +} + +mod map { + use super::*; +} + +mod mapAttrs { + use super::*; +} + +mod match_ { + use super::*; +} + +mod mul { + use super::*; +} + +mod outputOf { + use super::*; +} + +mod parseDrvName { + use super::*; +} + +mod parseFlakeRef { + use super::*; +} + +mod partition { + use super::*; +} + +mod path { + use super::*; +} + +mod pathExists { + use super::*; +} + +mod placeholder { + use super::*; +} + +mod readDir { + use super::*; +} + +mod readFile { + use super::*; +} + +mod readFileType { + use super::*; +} + +mod removeAttrs { + use super::*; +} + +mod replaceStrings { + use super::*; +} + +mod seq { + use super::*; +} + +mod sort { + use super::*; +} + +mod split { + use super::*; +} + +mod splitVersion { + use super::*; +} + +mod storePath { + use super::*; +} + +mod stringLength { + use super::*; +} + +mod sub { + use super::*; +} + +mod substring { + use super::*; +} + +mod tail { + use super::*; +} + +mod throw { + use super::*; +} + +mod toFile { + use super::*; +} + +mod toJSON { + use super::*; +} + +mod toPath { + use super::*; +} + +mod toString { + use super::*; +} + +mod toXML { + use super::*; +} + +mod trace { + use super::*; +} + +mod traceVerbose { + use super::*; +} + +mod tryEval { + use super::*; +} + +mod typeOf { + use super::*; +} + +mod unsafeDiscardOutputDependency { + use super::*; +} + +mod zipAttrsWith { + use super::*; +}