feat: persist JsRuntime
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1053,6 +1053,7 @@ dependencies = [
|
|||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"nix-js-macros",
|
"nix-js-macros",
|
||||||
|
"pin-project",
|
||||||
"regex",
|
"regex",
|
||||||
"rnix",
|
"rnix",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ derive_more = { version = "2", features = ["full"] }
|
|||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
string-interner = "0.19"
|
string-interner = "0.19"
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
|
pin-project = "1"
|
||||||
|
|
||||||
deno_core = "0.376"
|
deno_core = "0.376"
|
||||||
deno_error = "0.7"
|
deno_error = "0.7"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
use deno_core::JsRuntime;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
@@ -15,14 +17,55 @@ use downgrade::DowngradeCtx;
|
|||||||
mod downgrade;
|
mod downgrade;
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
|
ctx: Pin<Box<Ctx>>,
|
||||||
|
pub(crate) js_runtime: JsRuntime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Context {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 js_runtime = crate::runtime::new_js_runtime(ptr);
|
||||||
|
|
||||||
|
Self { ctx, js_runtime }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_code(&mut self, expr: &str) -> Result<Value> {
|
||||||
|
// Initialize `path_stack` with current directory for relative path resolution
|
||||||
|
let mut guard = PathDropGuard::new_cwd(self.ctx.as_mut());
|
||||||
|
let ctx = guard.as_ctx();
|
||||||
|
|
||||||
|
let root = rnix::Root::parse(expr);
|
||||||
|
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 code = ctx.get_ir(root).compile(Pin::get_ref(ctx.as_ref()));
|
||||||
|
let code = format!("Nix.force({})", code);
|
||||||
|
println!("[DEBUG] generated code: {}", &code);
|
||||||
|
crate::runtime::run(code, &mut self.js_runtime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pin_project::pin_project(PinnedDrop)]
|
||||||
|
pub struct Ctx {
|
||||||
irs: Vec<Ir>,
|
irs: Vec<Ir>,
|
||||||
symbols: DefaultStringInterner,
|
symbols: DefaultStringInterner,
|
||||||
global: NonNull<HashMap<SymId, ExprId>>,
|
global: NonNull<HashMap<SymId, ExprId>>,
|
||||||
path_stack: Vec<PathBuf>,
|
path_stack: Vec<PathBuf>,
|
||||||
|
#[pin]
|
||||||
|
_marker: std::marker::PhantomPinned,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Context {
|
#[pin_project::pinned_drop]
|
||||||
fn drop(&mut self) {
|
impl PinnedDrop for Ctx {
|
||||||
|
fn drop(self: Pin<&mut Self>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
drop(Box::from_raw(self.global.as_ptr()));
|
drop(Box::from_raw(self.global.as_ptr()));
|
||||||
}
|
}
|
||||||
@@ -30,32 +73,32 @@ impl Drop for Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PathDropGuard<'ctx> {
|
pub struct PathDropGuard<'ctx> {
|
||||||
ctx: &'ctx mut Context,
|
ctx: Pin<&'ctx mut Ctx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> PathDropGuard<'ctx> {
|
impl<'ctx> PathDropGuard<'ctx> {
|
||||||
pub fn new(path: PathBuf, ctx: &'ctx mut Context) -> Self {
|
pub fn new(path: PathBuf, mut ctx: Pin<&'ctx mut Ctx>) -> Self {
|
||||||
ctx.path_stack.push(path);
|
ctx.as_mut().project().path_stack.push(path);
|
||||||
Self { ctx }
|
Self { ctx }
|
||||||
}
|
}
|
||||||
pub fn new_cwd(ctx: &'ctx mut Context) -> Self {
|
pub fn new_cwd(mut ctx: Pin<&'ctx mut Ctx>) -> Self {
|
||||||
let cwd = std::env::current_dir().unwrap();
|
let cwd = std::env::current_dir().unwrap();
|
||||||
let virtual_file = cwd.join("__eval__.nix");
|
let virtual_file = cwd.join("__eval__.nix");
|
||||||
ctx.path_stack.push(virtual_file);
|
ctx.as_mut().project().path_stack.push(virtual_file);
|
||||||
Self { ctx }
|
Self { ctx }
|
||||||
}
|
}
|
||||||
pub fn as_ctx(&mut self) -> &mut Context {
|
pub fn as_ctx<'a>(&'a mut self) -> &'a mut Pin<&'ctx mut Ctx> {
|
||||||
self.ctx
|
&mut self.ctx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for PathDropGuard<'_> {
|
impl Drop for PathDropGuard<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.ctx.path_stack.pop();
|
self.ctx.as_mut().project().path_stack.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Context {
|
impl Default for Ctx {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
use crate::ir::{Attr, Builtins, Select, ToIr};
|
use crate::ir::{Attr, Builtins, Select, ToIr};
|
||||||
|
|
||||||
@@ -111,45 +154,33 @@ impl Default for Context {
|
|||||||
irs,
|
irs,
|
||||||
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
|
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
|
||||||
path_stack: Vec::new(),
|
path_stack: Vec::new(),
|
||||||
|
_marker: std::marker::PhantomPinned,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Ctx {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn downgrade_ctx<'a>(&'a mut self) -> DowngradeCtx<'a> {
|
pub fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> {
|
||||||
// SAFETY: `global` is readonly
|
// SAFETY: `global` is readonly
|
||||||
let global_ref = unsafe { self.global.as_ref() };
|
let global_ref = unsafe { self.global.as_ref() };
|
||||||
DowngradeCtx::new(self, global_ref)
|
DowngradeCtx::new(self, global_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_code(&mut self, expr: &str) -> Result<Value> {
|
|
||||||
// Initialize `path_stack` with current directory for relative path resolution
|
|
||||||
let mut guard = PathDropGuard::new_cwd(self);
|
|
||||||
let ctx = guard.as_ctx();
|
|
||||||
|
|
||||||
let root = rnix::Root::parse(expr);
|
|
||||||
if !root.errors().is_empty() {
|
|
||||||
return Err(Error::parse_error(root.errors().iter().join("; ")));
|
|
||||||
}
|
|
||||||
let root = ctx
|
|
||||||
.downgrade_ctx()
|
|
||||||
.downgrade(root.tree().expr().unwrap())?;
|
|
||||||
let code = ctx.get_ir(root).compile(ctx);
|
|
||||||
let code = format!("Nix.force({})", code);
|
|
||||||
println!("[DEBUG] generated code: {}", &code);
|
|
||||||
crate::runtime::run(code, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_current_dir(&self) -> PathBuf {
|
pub fn get_current_dir(&self) -> PathBuf {
|
||||||
self.path_stack.last().unwrap().parent().unwrap().to_path_buf()
|
self.path_stack
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.to_path_buf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodegenContext for Context {
|
impl CodegenContext for Ctx {
|
||||||
fn get_ir(&self, id: ExprId) -> &Ir {
|
fn get_ir(&self, id: ExprId) -> &Ir {
|
||||||
self.irs.get(id.0).unwrap()
|
self.irs.get(id.0).unwrap()
|
||||||
}
|
}
|
||||||
@@ -436,7 +467,9 @@ mod test {
|
|||||||
fn test_builtin_in_with() {
|
fn test_builtin_in_with() {
|
||||||
// Test builtins with 'with' expression
|
// Test builtins with 'with' expression
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval_code("with builtins; add 10 20").unwrap(),
|
Context::new()
|
||||||
|
.eval_code("with builtins; add 10 20")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(30))
|
Value::Const(Const::Int(30))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -460,11 +493,15 @@ mod test {
|
|||||||
Value::Const(Const::Bool(true))
|
Value::Const(Const::Bool(true))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval_code("builtins.isAttrs { a = 1; }").unwrap(),
|
Context::new()
|
||||||
|
.eval_code("builtins.isAttrs { a = 1; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Bool(true))
|
Value::Const(Const::Bool(true))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval_code("builtins.isFunction (x: x)").unwrap(),
|
Context::new()
|
||||||
|
.eval_code("builtins.isFunction (x: x)")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Bool(true))
|
Value::Const(Const::Bool(true))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -553,7 +590,9 @@ mod test {
|
|||||||
fn test_free_global_shadowing() {
|
fn test_free_global_shadowing() {
|
||||||
// Test shadowing of free globals
|
// Test shadowing of free globals
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval_code("let true = false; in true").unwrap(),
|
Context::new()
|
||||||
|
.eval_code("let true = false; in true")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Bool(false))
|
Value::Const(Const::Bool(false))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -699,7 +738,10 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(ctx.eval_code("7 / 3").unwrap(), Value::Const(Const::Int(2)));
|
assert_eq!(ctx.eval_code("7 / 3").unwrap(), Value::Const(Const::Int(2)));
|
||||||
|
|
||||||
assert_eq!(ctx.eval_code("10 / 3").unwrap(), Value::Const(Const::Int(3)));
|
assert_eq!(
|
||||||
|
ctx.eval_code("10 / 3").unwrap(),
|
||||||
|
Value::Const(Const::Int(3))
|
||||||
|
);
|
||||||
|
|
||||||
// Float division returns float
|
// Float division returns float
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -712,7 +754,10 @@ mod test {
|
|||||||
Value::Const(Const::Float(3.5))
|
Value::Const(Const::Float(3.5))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(ctx.eval_code("(-7) / 3").unwrap(), Value::Const(Const::Int(-2)));
|
assert_eq!(
|
||||||
|
ctx.eval_code("(-7) / 3").unwrap(),
|
||||||
|
Value::Const(Const::Int(-2))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::ir::{ArgId, Downgrade, DowngradeContext, ExprId, Ir, SymId, ToIr};
|
use crate::ir::{ArgId, Downgrade, DowngradeContext, ExprId, Ir, SymId, ToIr};
|
||||||
|
|
||||||
use super::Context;
|
use super::Ctx;
|
||||||
|
|
||||||
enum Scope<'ctx> {
|
enum Scope<'ctx> {
|
||||||
Global(&'ctx HashMap<SymId, ExprId>),
|
Global(&'ctx HashMap<SymId, ExprId>),
|
||||||
@@ -29,14 +31,14 @@ impl<'a, 'ctx> ScopeGuard<'a, 'ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct DowngradeCtx<'ctx> {
|
pub struct DowngradeCtx<'ctx> {
|
||||||
ctx: &'ctx mut Context,
|
ctx: Pin<&'ctx mut Ctx>,
|
||||||
irs: Vec<Option<Ir>>,
|
irs: Vec<Option<Ir>>,
|
||||||
scopes: Vec<Scope<'ctx>>,
|
scopes: Vec<Scope<'ctx>>,
|
||||||
arg_id: usize,
|
arg_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> DowngradeCtx<'ctx> {
|
impl<'ctx> DowngradeCtx<'ctx> {
|
||||||
pub fn new(ctx: &'ctx mut Context, global: &'ctx HashMap<SymId, ExprId>) -> Self {
|
pub fn new(ctx: Pin<&'ctx mut Ctx>, global: &'ctx HashMap<SymId, ExprId>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
scopes: vec![Scope::Global(global)],
|
scopes: vec![Scope::Global(global)],
|
||||||
irs: vec![],
|
irs: vec![],
|
||||||
@@ -59,7 +61,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_sym(&mut self, sym: String) -> SymId {
|
fn new_sym(&mut self, sym: String) -> SymId {
|
||||||
self.ctx.symbols.get_or_intern(sym)
|
self.ctx.as_mut().project().symbols.get_or_intern(sym)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sym(&self, id: SymId) -> &str {
|
fn get_sym(&self, id: SymId) -> &str {
|
||||||
@@ -133,6 +135,8 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
||||||
let root = root.downgrade(&mut self)?;
|
let root = root.downgrade(&mut self)?;
|
||||||
self.ctx
|
self.ctx
|
||||||
|
.as_mut()
|
||||||
|
.project()
|
||||||
.irs
|
.irs
|
||||||
.extend(self.irs.into_iter().map(Option::unwrap));
|
.extend(self.irs.into_iter().map(Option::unwrap));
|
||||||
Ok(root)
|
Ok(root)
|
||||||
|
|||||||
@@ -94,14 +94,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
|||||||
parts_ast
|
parts_ast
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
ast::InterpolPart::Literal(lit) => {
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
|
||||||
Ok(ctx.new_expr(
|
|
||||||
Str {
|
Str {
|
||||||
val: lit.to_string(),
|
val: lit.to_string(),
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
))
|
)),
|
||||||
}
|
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
ast::InterpolPart::Interpolation(interpol) => {
|
||||||
interpol.expr().unwrap().downgrade(ctx)
|
interpol.expr().unwrap().downgrade(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|
||||||
use deno_core::{
|
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||||
Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8
|
|
||||||
};
|
|
||||||
use deno_error::js_error_wrapper;
|
use deno_error::js_error_wrapper;
|
||||||
|
|
||||||
use crate::codegen::{CodegenContext, Compile};
|
use crate::codegen::{CodegenContext, Compile};
|
||||||
use crate::context::{Context, PathDropGuard};
|
use crate::context::{Ctx, PathDropGuard};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::ir::DowngradeContext;
|
use crate::ir::DowngradeContext;
|
||||||
use crate::value::{AttrSet, Const, List, Symbol, Value};
|
use crate::value::{AttrSet, Const, List, Symbol, Value};
|
||||||
@@ -18,12 +17,10 @@ pub trait RuntimeContext {
|
|||||||
fn split<Ctx: DowngradeContext>(&mut self) -> (&mut JsRuntime, &mut Ctx);
|
fn split<Ctx: DowngradeContext>(&mut self) -> (&mut JsRuntime, &mut Ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nix_runtime(ctx: &mut Context) -> Extension {
|
fn nix_runtime(ctx: NonNull<Ctx>) -> Extension {
|
||||||
const ESM: &[ExtensionFileSource] =
|
const ESM: &[ExtensionFileSource] =
|
||||||
&deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js");
|
&deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js");
|
||||||
|
|
||||||
// TODO: SAFETY
|
|
||||||
let ptr = unsafe { NonNull::new_unchecked(ctx) };
|
|
||||||
Extension {
|
Extension {
|
||||||
name: "nix_runtime",
|
name: "nix_runtime",
|
||||||
esm_files: Cow::Borrowed(ESM),
|
esm_files: Cow::Borrowed(ESM),
|
||||||
@@ -35,7 +32,7 @@ fn nix_runtime(ctx: &mut Context) -> Extension {
|
|||||||
op_resolve_path(),
|
op_resolve_path(),
|
||||||
]),
|
]),
|
||||||
op_state_fn: Some(Box::new(move |state| {
|
op_state_fn: Some(Box::new(move |state| {
|
||||||
state.put(RefCell::new(ptr));
|
state.put(RefCell::new(ctx));
|
||||||
})),
|
})),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -74,28 +71,22 @@ js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
|
|||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
#[string]
|
#[string]
|
||||||
fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result<String, NixError> {
|
fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result<String, NixError> {
|
||||||
let mut ptr = state.borrow::<RefCell<NonNull<Context>>>().borrow_mut();
|
let mut ptr = state.borrow::<RefCell<NonNull<Ctx>>>().borrow_mut();
|
||||||
let ctx = unsafe { ptr.as_mut() };
|
let ctx = unsafe { Pin::new_unchecked(ptr.as_mut()) };
|
||||||
|
|
||||||
// 1. Resolve path relative to current file (or CWD if top-level)
|
|
||||||
let current_dir = ctx.get_current_dir();
|
let current_dir = ctx.get_current_dir();
|
||||||
let absolute_path = current_dir
|
let absolute_path = current_dir
|
||||||
.join(&path)
|
.join(&path)
|
||||||
.canonicalize()
|
.canonicalize()
|
||||||
.map_err(|e| -> NixError {
|
.map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() })?;
|
||||||
format!("Failed to resolve path {}: {}", path, e).into()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// 2. Psh to stack for nested imports (RAII guard ensures pop on drop)
|
|
||||||
let mut guard = PathDropGuard::new(absolute_path.clone(), ctx);
|
let mut guard = PathDropGuard::new(absolute_path.clone(), ctx);
|
||||||
let ctx = guard.as_ctx();
|
let ctx = guard.as_ctx();
|
||||||
|
|
||||||
// 3. Read file
|
|
||||||
let content = std::fs::read_to_string(&absolute_path).map_err(|e| -> NixError {
|
let content = std::fs::read_to_string(&absolute_path).map_err(|e| -> NixError {
|
||||||
format!("Failed to read {}: {}", absolute_path.display(), e).into()
|
format!("Failed to read {}: {}", absolute_path.display(), e).into()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// 4. Parse
|
|
||||||
let root = rnix::Root::parse(&content);
|
let root = rnix::Root::parse(&content);
|
||||||
if !root.errors().is_empty() {
|
if !root.errors().is_empty() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@@ -106,18 +97,17 @@ fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result
|
|||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Downgrade to IR
|
|
||||||
let expr = root
|
let expr = root
|
||||||
.tree()
|
.tree()
|
||||||
.expr()
|
.expr()
|
||||||
.ok_or_else(|| -> NixError { "No expression in file".to_string().into() })?;
|
.ok_or_else(|| -> NixError { "No expression in file".to_string().into() })?;
|
||||||
let expr_id = ctx
|
let expr_id = ctx
|
||||||
|
.as_mut()
|
||||||
.downgrade_ctx()
|
.downgrade_ctx()
|
||||||
.downgrade(expr)
|
.downgrade(expr)
|
||||||
.map_err(|e| -> NixError { format!("Downgrade error: {}", e).into() })?;
|
.map_err(|e| -> NixError { format!("Downgrade error: {}", e).into() })?;
|
||||||
|
|
||||||
// 6. Codegen - returns JS code string
|
Ok(ctx.get_ir(expr_id).compile(Pin::get_ref(ctx.as_ref())))
|
||||||
Ok(ctx.get_ir(expr_id).compile(ctx))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
@@ -134,8 +124,11 @@ fn op_path_exists(#[string] path: String) -> bool {
|
|||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
#[string]
|
#[string]
|
||||||
fn op_resolve_path(state: &mut OpState, #[string] path: String) -> std::result::Result<String, NixError> {
|
fn op_resolve_path(
|
||||||
let ptr = state.borrow::<RefCell<NonNull<Context>>>().borrow();
|
state: &mut OpState,
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<String, NixError> {
|
||||||
|
let ptr = state.borrow::<RefCell<NonNull<Ctx>>>().borrow();
|
||||||
let ctx = unsafe { ptr.as_ref() };
|
let ctx = unsafe { ptr.as_ref() };
|
||||||
|
|
||||||
// If already absolute, return as-is
|
// If already absolute, return as-is
|
||||||
@@ -206,7 +199,7 @@ impl<'a, 'b> RuntimeCtx<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_js_runtime(ctx: &mut Context) -> JsRuntime {
|
pub fn new_js_runtime(ctx: NonNull<Ctx>) -> JsRuntime {
|
||||||
// Initialize V8 once
|
// Initialize V8 once
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
@@ -223,9 +216,7 @@ pub fn new_js_runtime(ctx: &mut Context) -> JsRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main entry point
|
// Main entry point
|
||||||
pub fn run(script: String, ctx: &mut Context) -> Result<Value> {
|
pub fn run(script: String, runtime: &mut JsRuntime) -> Result<Value> {
|
||||||
let mut runtime = new_js_runtime(ctx);
|
|
||||||
|
|
||||||
// Execute user script
|
// Execute user script
|
||||||
let global_value = runtime
|
let global_value = runtime
|
||||||
.execute_script("<eval>", script)
|
.execute_script("<eval>", script)
|
||||||
@@ -322,10 +313,7 @@ fn is_thunk<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a function is a primop
|
/// Check if a function is a primop
|
||||||
fn primop_name<'a, 'b>(
|
fn primop_name<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> Option<String> {
|
||||||
val: v8::Local<'a, v8::Value>,
|
|
||||||
ctx: &RuntimeCtx<'a, 'b>,
|
|
||||||
) -> Option<String> {
|
|
||||||
if !val.is_function() {
|
if !val.is_function() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -373,8 +361,13 @@ fn primop_app_name<'a, 'b>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn to_value_working() {
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::context::Context;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_value_working() {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run(
|
run(
|
||||||
@@ -382,7 +375,7 @@ fn to_value_working() {
|
|||||||
test: [1., 9223372036854775807n, true, false, 'hello world!']
|
test: [1., 9223372036854775807n, true, false, 'hello world!']
|
||||||
})"
|
})"
|
||||||
.into(),
|
.into(),
|
||||||
&mut ctx
|
&mut ctx.js_runtime
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(
|
Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(
|
||||||
@@ -396,4 +389,5 @@ fn to_value_working() {
|
|||||||
]))
|
]))
|
||||||
)])))
|
)])))
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user