refactor: tidy
This commit is contained in:
@@ -20,7 +20,7 @@ const typeName = (value: NixValue): string => {
|
||||
if (typeof val === "object") return "attribute set";
|
||||
|
||||
throw new TypeError(`Unknown Nix type: ${typeof val}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Force a value and assert it's a list
|
||||
@@ -52,7 +52,7 @@ export const force_function = (value: NixValue): NixFunction => {
|
||||
*/
|
||||
export const force_attrs = (value: NixValue): NixAttrs => {
|
||||
const forced = force(value);
|
||||
if (!isAttrs(forced)) {
|
||||
if (typeof forced !== "object" || Array.isArray(forced) || forced === null) {
|
||||
throw new TypeError(`Expected attribute set, got ${typeName(forced)}`);
|
||||
}
|
||||
return forced;
|
||||
|
||||
2
nix-js/runtime-ts/src/types/global.d.ts
vendored
2
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -1,7 +1,5 @@
|
||||
import type { NixRuntime } from "..";
|
||||
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
var Nix: NixRuntime;
|
||||
namespace Deno {
|
||||
|
||||
@@ -18,7 +18,7 @@ mod downgrade;
|
||||
|
||||
pub struct Context {
|
||||
ctx: Pin<Box<Ctx>>,
|
||||
pub(crate) runtime: Runtime,
|
||||
runtime: Runtime,
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
@@ -27,10 +27,20 @@ impl Default for Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CtxPtr(NonNull<Ctx>);
|
||||
impl CtxPtr {
|
||||
pub(crate) unsafe fn as_ref(&self) -> &Ctx {
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
pub(crate) unsafe fn as_mut(&mut self) -> Pin<&mut Ctx> {
|
||||
unsafe { Pin::new_unchecked(self.0.as_mut()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Self {
|
||||
let mut ctx = Box::pin(Ctx::new());
|
||||
let ptr = unsafe { NonNull::new_unchecked(Pin::get_unchecked_mut(ctx.as_mut())) };
|
||||
let ptr = unsafe { CtxPtr(NonNull::new_unchecked(Pin::get_unchecked_mut(ctx.as_mut()))) };
|
||||
let runtime = Runtime::new(ptr);
|
||||
|
||||
Self { ctx, runtime }
|
||||
@@ -45,16 +55,24 @@ impl Context {
|
||||
if !root.errors().is_empty() {
|
||||
return Err(Error::parse_error(root.errors().iter().join("; ")));
|
||||
}
|
||||
let root = ctx.as_mut().downgrade_ctx().downgrade(root.tree().expr().unwrap())?;
|
||||
let root = ctx
|
||||
.as_mut()
|
||||
.downgrade_ctx()
|
||||
.downgrade(root.tree().expr().unwrap())?;
|
||||
let code = ctx.get_ir(root).compile(Pin::get_ref(ctx.as_ref()));
|
||||
let code = format!("Nix.force({})", code);
|
||||
println!("[DEBUG] generated code: {}", &code);
|
||||
self.runtime.eval(code)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn eval_js(&mut self, code: String) -> Result<Value> {
|
||||
self.runtime.eval(code)
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project(PinnedDrop)]
|
||||
pub struct Ctx {
|
||||
pub(crate) struct Ctx {
|
||||
irs: Vec<Ir>,
|
||||
symbols: DefaultStringInterner,
|
||||
global: NonNull<HashMap<SymId, ExprId>>,
|
||||
@@ -72,22 +90,22 @@ impl PinnedDrop for Ctx {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PathDropGuard<'ctx> {
|
||||
pub(crate) struct PathDropGuard<'ctx> {
|
||||
ctx: Pin<&'ctx mut Ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx> PathDropGuard<'ctx> {
|
||||
pub fn new(path: PathBuf, mut ctx: Pin<&'ctx mut Ctx>) -> Self {
|
||||
pub(crate) fn new(path: PathBuf, mut ctx: Pin<&'ctx mut Ctx>) -> Self {
|
||||
ctx.as_mut().project().path_stack.push(path);
|
||||
Self { ctx }
|
||||
}
|
||||
pub fn new_cwd(mut ctx: Pin<&'ctx mut Ctx>) -> Self {
|
||||
pub(crate) fn new_cwd(mut ctx: Pin<&'ctx mut Ctx>) -> Self {
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let virtual_file = cwd.join("__eval__.nix");
|
||||
ctx.as_mut().project().path_stack.push(virtual_file);
|
||||
Self { ctx }
|
||||
}
|
||||
pub fn as_ctx<'a>(&'a mut self) -> &'a mut Pin<&'ctx mut Ctx> {
|
||||
pub(crate) fn as_ctx<'a>(&'a mut self) -> &'a mut Pin<&'ctx mut Ctx> {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
||||
@@ -165,7 +183,6 @@ impl Ctx {
|
||||
}
|
||||
|
||||
pub fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> {
|
||||
// SAFETY: `global` is readonly
|
||||
let global_ref = unsafe { self.global.as_ref() };
|
||||
DowngradeCtx::new(self, global_ref)
|
||||
}
|
||||
@@ -827,14 +844,14 @@ mod test {
|
||||
|
||||
let main_path = temp_dir.path().join("main.nix");
|
||||
let main_content = r#"
|
||||
let
|
||||
lib = import ./lib.nix;
|
||||
helper = import ./subdir/helper.nix;
|
||||
in {
|
||||
result1 = lib.multiply 3 4;
|
||||
result2 = helper.subtract 10 3;
|
||||
}
|
||||
"#;
|
||||
let
|
||||
lib = import ./lib.nix;
|
||||
helper = import ./subdir/helper.nix;
|
||||
in {
|
||||
result1 = lib.multiply 3 4;
|
||||
result2 = helper.subtract 10 3;
|
||||
}
|
||||
"#;
|
||||
std::fs::write(&main_path, main_content).unwrap();
|
||||
|
||||
let expr = format!(r#"let x = import "{}"; in x.result1"#, main_path.display());
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::Once;
|
||||
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8};
|
||||
use deno_error::js_error_wrapper;
|
||||
|
||||
use crate::codegen::{CodegenContext, Compile};
|
||||
use crate::context::{Ctx, PathDropGuard};
|
||||
use crate::context::{CtxPtr, PathDropGuard};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ir::DowngradeContext;
|
||||
use crate::value::{AttrSet, Const, List, Symbol, Value};
|
||||
|
||||
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
|
||||
type LocalValue<'a> = v8::Local<'a, v8::Value>;
|
||||
type LocalSymbol<'a> = v8::Local<'a, v8::Symbol>;
|
||||
|
||||
fn runtime_extension(ctx: NonNull<Ctx>) -> Extension {
|
||||
fn runtime_extension(ctx: CtxPtr) -> Extension {
|
||||
const ESM: &[ExtensionFileSource] =
|
||||
&deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js");
|
||||
const OPS: &[OpDecl] = &[
|
||||
op_import(),
|
||||
op_read_file(),
|
||||
op_path_exists(),
|
||||
op_resolve_path(),
|
||||
];
|
||||
|
||||
Extension {
|
||||
name: "nix_runtime",
|
||||
esm_files: Cow::Borrowed(ESM),
|
||||
esm_entry_point: Some("ext:nix_runtime/runtime.js"),
|
||||
ops: Cow::Owned(vec![
|
||||
op_import(),
|
||||
op_read_file(),
|
||||
op_path_exists(),
|
||||
op_resolve_path(),
|
||||
]),
|
||||
ops: Cow::Borrowed(OPS),
|
||||
op_state_fn: Some(Box::new(move |state| {
|
||||
state.put(RefCell::new(ctx));
|
||||
state.put(ctx);
|
||||
})),
|
||||
enabled: true,
|
||||
..Default::default()
|
||||
@@ -58,19 +59,18 @@ impl std::error::Error for SimpleErrorWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
|
||||
impl From<String> for NixError {
|
||||
fn from(value: String) -> Self {
|
||||
NixError(SimpleErrorWrapper(value))
|
||||
}
|
||||
}
|
||||
|
||||
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result<String, NixError> {
|
||||
let mut ptr = state.borrow::<RefCell<NonNull<Ctx>>>().borrow_mut();
|
||||
let ctx = unsafe { Pin::new_unchecked(ptr.as_mut()) };
|
||||
let ptr = state.borrow_mut::<CtxPtr>();
|
||||
let ctx = unsafe { ptr.as_mut() };
|
||||
|
||||
let current_dir = ctx.get_current_dir();
|
||||
let absolute_path = current_dir
|
||||
@@ -126,7 +126,7 @@ fn op_resolve_path(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixError> {
|
||||
let ptr = state.borrow::<RefCell<NonNull<Ctx>>>().borrow();
|
||||
let ptr = state.borrow::<CtxPtr>();
|
||||
let ctx = unsafe { ptr.as_ref() };
|
||||
|
||||
// If already absolute, return as-is
|
||||
@@ -144,14 +144,14 @@ fn op_resolve_path(
|
||||
.map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() })
|
||||
}
|
||||
|
||||
pub struct Runtime {
|
||||
pub(crate) struct Runtime {
|
||||
js_runtime: JsRuntime,
|
||||
is_thunk_symbol: v8::Global<v8::Symbol>,
|
||||
primop_metadata_symbol: v8::Global<v8::Symbol>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub fn new(ctx: NonNull<Ctx>) -> Self {
|
||||
pub(crate) fn new(ctx: CtxPtr) -> Self {
|
||||
// Initialize V8 once
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(|| {
|
||||
@@ -178,7 +178,7 @@ impl Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval(&mut self, script: String) -> Result<Value> {
|
||||
pub(crate) fn eval(&mut self, script: String) -> Result<Value> {
|
||||
let global_value = self
|
||||
.js_runtime
|
||||
.execute_script("<eval>", script)
|
||||
@@ -190,13 +190,16 @@ impl Runtime {
|
||||
let is_thunk_symbol = v8::Local::new(scope, &self.is_thunk_symbol);
|
||||
let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol);
|
||||
|
||||
Ok(to_value(local_value, scope, is_thunk_symbol, primop_metadata_symbol))
|
||||
Ok(to_value(
|
||||
local_value,
|
||||
scope,
|
||||
is_thunk_symbol,
|
||||
primop_metadata_symbol,
|
||||
))
|
||||
}
|
||||
|
||||
/// get (IS_THUNK, PRIMOP_METADATA)
|
||||
fn get_symbols(
|
||||
scope: &v8::PinnedRef<'_, v8::HandleScope<'_>>,
|
||||
) -> (v8::Global<v8::Symbol>, v8::Global<v8::Symbol>) {
|
||||
fn get_symbols(scope: &ScopeRef) -> (v8::Global<v8::Symbol>, v8::Global<v8::Symbol>) {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let nix_key = v8::String::new(scope, "Nix").unwrap();
|
||||
let nix_obj = global
|
||||
@@ -220,10 +223,10 @@ impl Runtime {
|
||||
}
|
||||
|
||||
fn to_value<'a>(
|
||||
val: v8::Local<'a, v8::Value>,
|
||||
val: LocalValue<'a>,
|
||||
scope: &ScopeRef<'a, '_>,
|
||||
is_thunk_symbol: v8::Local<'a, v8::Symbol>,
|
||||
primop_metadata_symbol: v8::Local<'a, v8::Symbol>,
|
||||
is_thunk_symbol: LocalSymbol<'a>,
|
||||
primop_metadata_symbol: LocalSymbol<'a>,
|
||||
) -> Value {
|
||||
match () {
|
||||
_ if val.is_big_int() => {
|
||||
@@ -257,10 +260,8 @@ fn to_value<'a>(
|
||||
Value::List(List::new(list))
|
||||
}
|
||||
_ if val.is_function() => {
|
||||
if let Some(name) = primop_app_name(val, scope, primop_metadata_symbol) {
|
||||
Value::PrimOpApp(name)
|
||||
} else if let Some(name) = primop_name(val, scope, primop_metadata_symbol) {
|
||||
Value::PrimOp(name)
|
||||
if let Some(primop) = to_primop(val, scope, primop_metadata_symbol) {
|
||||
primop
|
||||
} else {
|
||||
Value::Func
|
||||
}
|
||||
@@ -280,7 +281,10 @@ fn to_value<'a>(
|
||||
let key = keys.get_index(scope, i).unwrap();
|
||||
let val = val.get(scope, key).unwrap();
|
||||
let key = key.to_rust_string_lossy(scope);
|
||||
(Symbol::new(key), to_value(val, scope, is_thunk_symbol, primop_metadata_symbol))
|
||||
(
|
||||
Symbol::new(key),
|
||||
to_value(val, scope, is_thunk_symbol, primop_metadata_symbol),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Value::AttrSet(AttrSet::new(attrs))
|
||||
@@ -289,7 +293,7 @@ fn to_value<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_thunk<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> bool {
|
||||
fn is_thunk<'a>(val: LocalValue<'a>, scope: &ScopeRef<'a, '_>, symbol: LocalSymbol<'a>) -> bool {
|
||||
if !val.is_object() {
|
||||
return false;
|
||||
}
|
||||
@@ -298,39 +302,31 @@ fn is_thunk<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol:
|
||||
matches!(obj.get(scope, symbol.into()), Some(v) if v.is_true())
|
||||
}
|
||||
|
||||
fn primop_name<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option<String> {
|
||||
fn to_primop<'a>(
|
||||
val: LocalValue<'a>,
|
||||
scope: &ScopeRef<'a, '_>,
|
||||
symbol: LocalSymbol<'a>,
|
||||
) -> Option<Value> {
|
||||
if !val.is_function() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let obj = val.to_object(scope).unwrap();
|
||||
let metadata = obj.get(scope, symbol.into())?.to_object(scope)?;
|
||||
|
||||
if let Some(metadata) = obj.get(scope, symbol.into())
|
||||
&& let Some(metadata_obj) = metadata.to_object(scope)
|
||||
&& let Some(name_key) = v8::String::new(scope, "name")
|
||||
&& let Some(name_val) = metadata_obj.get(scope, name_key.into())
|
||||
{
|
||||
Some(name_val.to_rust_string_lossy(scope))
|
||||
let name_key = v8::String::new(scope, "name")?;
|
||||
let name = metadata
|
||||
.get(scope, name_key.into())?
|
||||
.to_rust_string_lossy(scope);
|
||||
|
||||
let applied_key = v8::String::new(scope, "applied")?;
|
||||
let applied_val = metadata.get(scope, applied_key.into())?;
|
||||
let applied = applied_val.to_number(scope)?.value();
|
||||
|
||||
if applied == 0.0 {
|
||||
Some(Value::PrimOp(name))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn primop_app_name<'a>(val: v8::Local<'a, v8::Value>, scope: &ScopeRef<'a, '_>, symbol: v8::Local<'a, v8::Symbol>) -> Option<String> {
|
||||
let name = primop_name(val, scope, symbol)?;
|
||||
|
||||
let obj = val.to_object(scope).unwrap();
|
||||
|
||||
if let Some(metadata) = obj.get(scope, symbol.into())
|
||||
&& let Some(metadata_obj) = metadata.to_object(scope)
|
||||
&& let Some(applied_key) = v8::String::new(scope, "applied")
|
||||
&& let Some(applied_val) = metadata_obj.get(scope, applied_key.into())
|
||||
&& let Some(applied_num) = applied_val.to_number(scope)
|
||||
&& applied_num.value() > 0.0
|
||||
{
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
Some(Value::PrimOpApp(name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,10 +339,10 @@ mod test {
|
||||
fn to_value_working() {
|
||||
let mut ctx = Context::new();
|
||||
assert_eq!(
|
||||
ctx.runtime.eval(
|
||||
ctx.eval_js(
|
||||
"({
|
||||
test: [1., 9223372036854775807n, true, false, 'hello world!']
|
||||
})"
|
||||
test: [1., 9223372036854775807n, true, false, 'hello world!']
|
||||
})"
|
||||
.into(),
|
||||
)
|
||||
.unwrap(),
|
||||
|
||||
Reference in New Issue
Block a user