Skip to content

Commit

Permalink
Added the imports builtin
Browse files Browse the repository at this point in the history
  • Loading branch information
arduano committed Mar 30, 2024
1 parent 1d120a9 commit a0996e6
Show file tree
Hide file tree
Showing 15 changed files with 145 additions and 81 deletions.
5 changes: 2 additions & 3 deletions CONTRIBUTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ automatically set up. You can verify this with:
cargo --version
```

Rix uses `nixrt` (a JavaScript library), which it loads from the location
specified in the `RIX_NIXRT_JS_MODULE` environment variable. `direnv` makes sure
that this environment variable is set correctly.
Rix uses `nixrt` (a JavaScript library), which is located in the `nixrt-rt` folder
in this repository. It needs to be built before `rix` can be run.

## Editor

Expand Down
1 change: 0 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
name = "rix";
buildInputs = rix-deps ++ nixjs-rt-deps;
shellHook = ''
export RIX_NIXRT_JS_MODULE=nixjs-rt/dist/lib.mjs
'';
};
});
Expand Down
21 changes: 21 additions & 0 deletions nixjs-rt/src/builtins/builtins.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Attrset,
EvalCtx,
EvalException,
FALSE,
Lambda,
Expand All @@ -9,6 +10,7 @@ import {
NixList,
NixString,
NixType,
Path,
TRUE,
} from "../lib";

Expand All @@ -22,6 +24,25 @@ export function getBuiltins() {
);
},

import: (path) => {
const pathStrict = path.toStrict();

if (!(pathStrict instanceof Path || pathStrict instanceof NixString)) {
throw new EvalException(
`Cannot import a value of type '${pathStrict.typeOf()}'.`,
);
}

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);
},

add: (lhs): Lambda => {
return new Lambda((rhs) => {
let lhsStrict = lhs.toStrict();
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/builtins/tests/abort.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "../../lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/builtins/tests/add.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "../../lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/builtins/tests/all.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "../../lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/builtins/tests/any.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "../../lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/builtins/tests/attrNames.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "../../lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/builtins/tests/attrValues.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "../../lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/builtins/tests/head.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "../../lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
3 changes: 2 additions & 1 deletion nixjs-rt/src/lib.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, expect, test } from "@jest/globals";
import n, {
import * as n from "./lib";
import {
Attrset,
attrset,
AttrsetBody,
Expand Down
45 changes: 0 additions & 45 deletions nixjs-rt/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1237,48 +1237,3 @@ export function withExpr(
): any {
return body(evalCtx.withNonShadowingScope(namespace));
}

export default {
// Constants:
EMPTY_ATTRSET,
FALSE,
NULL,
TRUE,

// Types:
Attrset,
EvalCtx,
EvalException,
Lambda,
Lazy,
LazyAttrset,
NixBool,
NixFloat,
NixInt,
NixList,
NixNull,
NixString,
Path,
StrictAttrset,

// Attrset:
attrset,
recAttrset,

// Lambda:
paramLambda,
patternLambda,

// Let in:
letIn,

// Path:
toPath,

// Utilies:
toStrict: recursiveStrict,
toStrictAttrset: recursiveStrictAttrset,

// With:
withExpr,
};
4 changes: 1 addition & 3 deletions src/eval/emit_js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ use rowan::ast::AstNode;
pub fn emit_module(nix_expr: &str) -> Result<String, String> {
let root = rnix::Root::parse(nix_expr).tree();
let root_expr = root.expr().expect("Not implemented");
let nixrt_js_module = env!("RIX_NIXRT_JS_MODULE");
let mut out_src = format!("import n from '{nixrt_js_module}';\n");
out_src += "export const __nixrt = n;\n";
let mut out_src = String::new();
out_src += "export const __nixValue = (ctx) => ";
emit_expr(&root_expr, &mut out_src)?;
out_src += ";\n";
Expand Down
118 changes: 97 additions & 21 deletions src/eval/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::env::current_dir;
use std::path::Path;
use std::sync::Once;

use v8::ModuleStatus;
use v8::{HandleScope, Local, ModuleStatus, Object};

use crate::eval::types::EvalResult;

Expand All @@ -14,15 +14,43 @@ static INIT_V8: Once = Once::new();

pub fn evaluate(nix_expr: &str) -> EvalResult {
initialize_v8();
// Declare the V8 execution context
let isolate = &mut v8::Isolate::new(Default::default());
let scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
let global = context.global(scope);

let import_module_attr = v8::String::new(scope, "importNixModule").unwrap();
let import_module_fn = v8::Function::new(scope, import_nix_module).unwrap();
global
.set(scope, import_module_attr.into(), import_module_fn.into())
.unwrap();

// Execute the Nix runtime JS module, get its exports
let nixjs_rt_str = include_str!("../../nixjs-rt/dist/lib.mjs");
let nixjs_rt_obj = exec_module(nixjs_rt_str, scope).unwrap();

// Set them to a global variable
let nixrt_attr = v8::String::new(scope, "n").unwrap();
global
.set(scope, nixrt_attr.into(), nixjs_rt_obj.into())
.unwrap();

let root_nix_fn = eval_nix_fn_from_string(scope, nix_expr)?;

nix_value_from_module(scope, root_nix_fn, nixjs_rt_obj)
}

fn eval_nix_fn_from_string<'s>(
scope: &mut HandleScope<'s>,
nix_expr: &str,
) -> Result<v8::Local<'s, v8::Function>, String> {
let source_str = emit_module(nix_expr)?;
let source_v8 = to_v8_source(scope, &source_str, "<eval string>");
let module = v8::script_compiler::compile_module(scope, source_v8).unwrap();
let module_source_v8 = to_v8_source(scope, &source_str, "<eval string>");
let module = v8::script_compiler::compile_module(scope, module_source_v8).unwrap();

let resolve_module_callback = |_, _, _, _| panic!("Module resolution not supported.");
if module
.instantiate_module(scope, resolve_module_callback)
.is_none()
Expand All @@ -42,7 +70,64 @@ pub fn evaluate(nix_expr: &str) -> EvalResult {
}

let namespace_obj = module.get_module_namespace().to_object(scope).unwrap();
nix_value_from_module(scope, &namespace_obj)

let nix_value_attr = v8::String::new(scope, "__nixValue").unwrap();
let Some(nix_value) = namespace_obj.get(scope, nix_value_attr.into()) else {
todo!(
"Could not find the nix value: {:?}",
namespace_obj.to_rust_string_lossy(scope)
)
};
let nix_value: v8::Local<v8::Function> =
nix_value.try_into().expect("Nix value must be a function.");

Ok(nix_value)
}

fn import_nix_module<'s>(
scope: &mut HandleScope<'s>,
args: v8::FunctionCallbackArguments<'s>,
mut ret: v8::ReturnValue,
) {
let module_path = args.get(0).to_rust_string_lossy(scope);
let module_source_str = std::fs::read_to_string(&module_path).unwrap();

let nix_fn = eval_nix_fn_from_string(scope, &module_source_str);

let nix_fn = match nix_fn {
Ok(nix_fn) => nix_fn,
Err(err) => {
let err_str = v8::String::new(scope, &err).unwrap();
let err_obj = v8::Exception::error(scope, err_str.into());
ret.set(err_obj.into());
return;
}
};

ret.set(nix_fn.into());
}

fn exec_module<'a>(
code: &str,
scope: &mut v8::HandleScope<'a>,
) -> Result<Local<'a, Object>, String> {
let source = to_v8_source(scope, code, "<eval string>");
let module = v8::script_compiler::compile_module(scope, source).unwrap();

if module
.instantiate_module(scope, resolve_module_callback)
.is_none()
{
return Err("Instantiation failure.".to_owned());
}

if module.evaluate(scope).is_none() {
return Err("Evaluation failure.".to_owned());
}

let obj = module.get_module_namespace().to_object(scope).unwrap();

Ok(obj)
}

fn initialize_v8() {
Expand All @@ -55,20 +140,10 @@ fn initialize_v8() {

fn nix_value_from_module(
scope: &mut v8::ContextScope<v8::HandleScope>,
namespace_obj: &v8::Local<v8::Object>,
nix_value: v8::Local<v8::Function>,
nixjs_rt_obj: v8::Local<v8::Object>,
) -> EvalResult {
let nix_value_attr = v8::String::new(scope, "__nixValue").unwrap();
let Some(nix_value) = namespace_obj.get(scope, nix_value_attr.into()) else {
todo!(
"Could not find the nix value: {:?}",
namespace_obj.to_rust_string_lossy(scope)
)
};
let nix_value: v8::Local<v8::Function> =
nix_value.try_into().expect("Nix value must be a function.");

let nixrt_attr = v8::String::new(scope, "__nixrt").unwrap();
let nixrt: v8::Local<v8::Value> = namespace_obj.get(scope, nixrt_attr.into()).unwrap();
let nixrt: v8::Local<v8::Value> = nixjs_rt_obj.into();

let eval_ctx = create_eval_ctx(
scope,
Expand All @@ -80,10 +155,11 @@ fn nix_value_from_module(

let nix_value = call_js_function(scope, &nix_value, &[eval_ctx.into()])?;

let to_strict_fn: v8::Local<v8::Function> = try_get_js_object_key(scope, &nixrt, "toStrict")?
.expect("Could not find the function `toStrict` in `nixrt`.")
.try_into()
.expect("`n.toStrict` is not a function.");
let to_strict_fn: v8::Local<v8::Function> =
try_get_js_object_key(scope, &nixrt, "recursiveStrict")?
.expect("Could not find the function `recursiveStrict` in `nixrt`.")
.try_into()
.expect("`n.recursiveStrict` is not a function.");
let strict_nix_value = call_js_function(scope, &to_strict_fn, &[nix_value])?;

js_value_to_nix(scope, &nixrt, &strict_nix_value)
Expand Down
8 changes: 8 additions & 0 deletions src/eval/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,12 @@ mod tests {
Value::Bool(false)
);
}

#[test]
fn test_eval_builtin_() {
assert_eq!(
eval_ok("(builtins.import ./flake.nix).description"),
Value::Str("A reimplementation or nix in Rust.".into())
);
}
}

0 comments on commit a0996e6

Please sign in to comment.