diff --git a/bindings/jsonnet/src/import.rs b/bindings/jsonnet/src/import.rs index 10473336..1d4ee17a 100644 --- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -15,7 +15,7 @@ use std::{ use jrsonnet_evaluator::{ bail, error::{ErrorKind::*, Result}, - ImportResolver, + AsPathLike, ImportResolver, ResolvePath, }; use jrsonnet_gcmodule::Trace; use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath}; @@ -41,7 +41,7 @@ pub struct CallbackImportResolver { out: RefCell>>, } impl ImportResolver for CallbackImportResolver { - fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { + fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result { let base = if let Some(p) = from.downcast_ref::() { let mut o = p.path().to_owned(); o.pop(); @@ -54,7 +54,11 @@ impl ImportResolver for CallbackImportResolver { unreachable!("can't resolve this path"); }; let base = unsafe { crate::unparse_path(&base) }; - let rel = CString::new(path).unwrap(); + let rel = path.as_path(); + let rel = match rel { + ResolvePath::Str(s) => CString::new(s.as_bytes()).unwrap(), + ResolvePath::Path(p) => unsafe { crate::unparse_path(p) }, + }; let found_here: *mut c_char = null_mut(); let mut buf = null_mut(); diff --git a/bindings/jsonnet/src/lib.rs b/bindings/jsonnet/src/lib.rs index 2767c0c1..4b2241b2 100644 --- a/bindings/jsonnet/src/lib.rs +++ b/bindings/jsonnet/src/lib.rs @@ -27,7 +27,7 @@ use jrsonnet_evaluator::{ stack::set_stack_depth_limit, tb, trace::{CompactFormat, PathResolver, TraceFormat}, - FileImportResolver, IStr, ImportResolver, Result, State, Val, + AsPathLike, FileImportResolver, IStr, ImportResolver, Result, State, Val, }; use jrsonnet_gcmodule::Trace; use jrsonnet_parser::SourcePath; @@ -61,18 +61,18 @@ unsafe fn parse_path(input: &CStr) -> Cow { } } -unsafe fn unparse_path(input: &Path) -> Cow { +unsafe fn unparse_path(input: &Path) -> CString { #[cfg(target_family = "unix")] { use std::os::unix::ffi::OsStrExt; let str = CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it"); - Cow::Owned(str) + str } #[cfg(not(target_family = "unix"))] { let str = input.as_os_str().to_str().expect("bad utf-8"); let cstr = CString::new(str).expect("input has NUL inside"); - Cow::Owned(cstr) + cstr } } @@ -93,18 +93,14 @@ impl ImportResolver for VMImportResolver { self.inner.borrow().load_file_contents(resolved) } - fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { + fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result { self.inner.borrow().resolve_from(from, path) } - fn resolve_from_default(&self, path: &str) -> Result { + fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result { self.inner.borrow().resolve_from_default(path) } - fn resolve(&self, path: &Path) -> Result { - self.inner.borrow().resolve(path) - } - fn as_any(&self) -> &dyn Any { self } diff --git a/bindings/jsonnet/src/vars_tlas.rs b/bindings/jsonnet/src/vars_tlas.rs index 39abfc81..bbc9b720 100644 --- a/bindings/jsonnet/src/vars_tlas.rs +++ b/bindings/jsonnet/src/vars_tlas.rs @@ -3,7 +3,6 @@ use std::{ffi::CStr, os::raw::c_char}; use jrsonnet_evaluator::{function::TlaArg, IStr}; -use jrsonnet_parser::{ParserSettings, Source}; use crate::VM; @@ -84,14 +83,7 @@ pub unsafe extern "C" fn jsonnet_tla_code(vm: &mut VM, name: *const c_char, code let code = unsafe { CStr::from_ptr(code) }; let name: IStr = name.to_str().expect("name is not utf-8").into(); - let code: IStr = code.to_str().expect("code is not utf-8").into(); - let code = jrsonnet_parser::parse( - &code, - &ParserSettings { - source: Source::new_virtual(format!("").into(), code.clone()), - }, - ) - .expect("can't parse TLA code"); + let code: String = code.to_str().expect("code is not utf-8").to_owned(); - vm.tla_args.insert(name, TlaArg::Code(code)); + vm.tla_args.insert(name, TlaArg::InlineCode(code)); } diff --git a/cmds/jrsonnet/src/main.rs b/cmds/jrsonnet/src/main.rs index 415b9435..3dd5864f 100644 --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -182,7 +182,7 @@ fn main_real(opts: Opts) -> Result<(), Error> { let input_str = std::str::from_utf8(&input)?; s.evaluate_snippet("".to_owned(), input_str)? } else { - s.import(&input)? + s.import(input.as_str())? }; let tla = opts.tla.tla_opts()?; diff --git a/crates/jrsonnet-cli/src/stdlib.rs b/crates/jrsonnet-cli/src/stdlib.rs index 15689e2e..6504e36b 100644 --- a/crates/jrsonnet-cli/src/stdlib.rs +++ b/crates/jrsonnet-cli/src/stdlib.rs @@ -1,7 +1,7 @@ -use std::{fs::read_to_string, str::FromStr}; +use std::str::FromStr; use clap::Parser; -use jrsonnet_evaluator::{trace::PathResolver, Result}; +use jrsonnet_evaluator::{function::TlaArg, trace::PathResolver, Result}; use jrsonnet_stdlib::ContextInitializer; #[derive(Clone)] @@ -54,25 +54,20 @@ impl FromStr for ExtStr { #[derive(Clone)] pub struct ExtFile { pub name: String, - pub value: String, + pub path: String, } impl FromStr for ExtFile { type Err = String; fn from_str(s: &str) -> std::result::Result { - let out: Vec<&str> = s.split('=').collect(); - if out.len() != 2 { + let Some((name, path)) = s.split_once('=') else { return Err("bad ext-file syntax".to_owned()); - } - let file = read_to_string(out[1]); - match file { - Ok(content) => Ok(Self { - name: out[0].into(), - value: content, - }), - Err(e) => Err(format!("{e}")), - } + }; + Ok(Self { + name: name.into(), + path: path.into(), + }) } } @@ -110,16 +105,27 @@ impl StdOpts { } let ctx = ContextInitializer::new(PathResolver::new_cwd_fallback()); for ext in &self.ext_str { - ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into()); + ctx.settings_mut().ext_vars.insert( + ext.name.as_str().into(), + TlaArg::String(ext.value.as_str().into()), + ); } for ext in &self.ext_str_file { - ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into()); + ctx.settings_mut().ext_vars.insert( + ext.name.as_str().into(), + TlaArg::ImportStr(ext.path.clone()), + ); } for ext in &self.ext_code { - ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?; + ctx.settings_mut().ext_vars.insert( + ext.name.as_str().into(), + TlaArg::InlineCode(ext.value.clone()), + ); } for ext in &self.ext_code_file { - ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?; + ctx.settings_mut() + .ext_vars + .insert(ext.name.as_str().into(), TlaArg::Import(ext.path.clone())); } Ok(Some(ctx)) } diff --git a/crates/jrsonnet-cli/src/tla.rs b/crates/jrsonnet-cli/src/tla.rs index 6d1c4174..909ae645 100644 --- a/crates/jrsonnet-cli/src/tla.rs +++ b/crates/jrsonnet-cli/src/tla.rs @@ -1,11 +1,5 @@ use clap::Parser; -use jrsonnet_evaluator::{ - error::{ErrorKind, Result}, - function::TlaArg, - gc::GcHashMap, - IStr, -}; -use jrsonnet_parser::{ParserSettings, Source}; +use jrsonnet_evaluator::{error::Result, function::TlaArg, gc::GcHashMap, IStr}; use crate::{ExtFile, ExtStr}; @@ -34,35 +28,28 @@ pub struct TlaOpts { impl TlaOpts { pub fn tla_opts(&self) -> Result> { let mut out = GcHashMap::new(); - for (name, value) in self - .tla_str - .iter() - .map(|c| (&c.name, &c.value)) - .chain(self.tla_str_file.iter().map(|c| (&c.name, &c.value))) - { - out.insert(name.into(), TlaArg::String(value.into())); + for ext in &self.tla_str { + out.insert( + ext.name.as_str().into(), + TlaArg::String(ext.value.as_str().into()), + ); + } + for ext in &self.tla_str_file { + out.insert( + ext.name.as_str().into(), + TlaArg::ImportStr(ext.name.as_str().into()), + ); + } + for ext in &self.tla_code { + out.insert( + ext.name.as_str().into(), + TlaArg::InlineCode(ext.value.clone()), + ); } - for (name, code) in self - .tla_code - .iter() - .map(|c| (&c.name, &c.value)) - .chain(self.tla_code_file.iter().map(|c| (&c.name, &c.value))) - { - let source = Source::new_virtual(format!("").into(), code.into()); + for ext in &self.tla_code_file { out.insert( - (name as &str).into(), - TlaArg::Code( - jrsonnet_parser::parse( - code, - &ParserSettings { - source: source.clone(), - }, - ) - .map_err(|e| ErrorKind::ImportSyntaxError { - path: source, - error: Box::new(e), - })?, - ), + ext.name.as_str().into(), + TlaArg::Import(ext.path.clone()), ); } Ok(out) diff --git a/crates/jrsonnet-evaluator/src/async_import.rs b/crates/jrsonnet-evaluator/src/async_import.rs index d12dc4fb..2e50f935 100644 --- a/crates/jrsonnet-evaluator/src/async_import.rs +++ b/crates/jrsonnet-evaluator/src/async_import.rs @@ -236,8 +236,6 @@ pub trait AsyncImportResolver { ) -> impl Future> { async { self.resolve_from(&SourcePath::default(), path).await } } - /// Resolves absolute path, doesn't supports jpath and other fancy things - fn resolve(&self, path: &Path) -> impl Future>; /// Load resolved file /// This should only be called with value returned @@ -273,12 +271,6 @@ impl ImportResolver for ResolvedImportResolver { self.resolve_from(&SourcePath::default(), path) } - fn resolve(&self, path: &Path) -> crate::Result { - bail!(crate::error::ErrorKind::AbsoluteImportNotSupported( - path.to_owned() - )) - } - fn as_any(&self) -> &dyn std::any::Any { self } diff --git a/crates/jrsonnet-evaluator/src/error.rs b/crates/jrsonnet-evaluator/src/error.rs index 5b718c8f..304b281f 100644 --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -2,7 +2,6 @@ use std::{ cmp::Ordering, convert::Infallible, fmt::{Debug, Display}, - path::PathBuf, }; use jrsonnet_gcmodule::Trace; @@ -16,7 +15,7 @@ use crate::{ stdlib::format::FormatError, typed::TypeLocError, val::ConvertNumValueError, - ObjValue, + ObjValue, ResolvePathOwned, }; pub(crate) fn format_found(list: &[IStr], what: &str) -> String { @@ -180,9 +179,7 @@ pub enum ErrorKind { StandaloneSuper, #[error("can't resolve {1} from {0}")] - ImportFileNotFound(SourcePath, String), - #[error("can't resolve absolute {0}")] - AbsoluteImportFileNotFound(PathBuf), + ImportFileNotFound(SourcePath, ResolvePathOwned), #[error("resolved file not found: {:?}", .0)] ResolvedFileNotFound(SourcePath), #[error("can't import {0}: is a directory")] @@ -192,9 +189,7 @@ pub enum ErrorKind { #[error("import io error: {0}")] ImportIo(String), #[error("tried to import {1} from {0}, but imports are not supported")] - ImportNotSupported(SourcePath, String), - #[error("tried to import {0}, but absolute imports are not supported")] - AbsoluteImportNotSupported(PathBuf), + ImportNotSupported(SourcePath, ResolvePathOwned), #[error("can't import from virtual file")] CantImportFromVirtualFile, #[error( diff --git a/crates/jrsonnet-evaluator/src/evaluate/mod.rs b/crates/jrsonnet-evaluator/src/evaluate/mod.rs index f3e9ac50..5feb6a6c 100644 --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -680,7 +680,7 @@ pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result { }; let tmp = loc.clone().0; let s = ctx.state(); - let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?; + let resolved_path = s.resolve_from(tmp.source_path(), path)?; match i { Import(_) => in_frame( CallLocation::new(&loc), diff --git a/crates/jrsonnet-evaluator/src/function/arglike.rs b/crates/jrsonnet-evaluator/src/function/arglike.rs index ce45759c..2c3828f8 100644 --- a/crates/jrsonnet-evaluator/src/function/arglike.rs +++ b/crates/jrsonnet-evaluator/src/function/arglike.rs @@ -1,7 +1,7 @@ use hashbrown::HashMap; use jrsonnet_gcmodule::Trace; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ArgsDesc, LocExpr}; +use jrsonnet_parser::{ArgsDesc, LocExpr, SourceFifo, SourcePath}; use crate::{evaluate, gc::GcHashMap, typed::Typed, Context, Result, Thunk, Val}; @@ -40,22 +40,34 @@ impl OptionalContext for T where T: Typed + Clone {} #[derive(Clone, Trace)] pub enum TlaArg { String(IStr), - Code(LocExpr), Val(Val), Lazy(Thunk), + Import(String), + ImportStr(String), + InlineCode(String), } impl ArgLike for TlaArg { - fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result> { + fn evaluate_arg(&self, ctx: Context, _tailstrict: bool) -> Result> { match self { Self::String(s) => Ok(Thunk::evaluated(Val::string(s.clone()))), - Self::Code(code) => Ok(if tailstrict { - Thunk::evaluated(evaluate(ctx, code)?) - } else { - let code = code.clone(); - Thunk!(move || evaluate(ctx, &code)) - }), Self::Val(val) => Ok(Thunk::evaluated(val.clone())), Self::Lazy(lazy) => Ok(lazy.clone()), + Self::Import(p) => { + let resolved = ctx.state().resolve_from_default(&p.as_str())?; + Ok(Thunk!(move || ctx.state().import_resolved(resolved))) + } + Self::ImportStr(p) => { + let resolved = ctx.state().resolve_from_default(&p.as_str())?; + Ok(Thunk!(move || ctx + .state() + .import_resolved_str(resolved) + .map(Val::string))) + } + Self::InlineCode(p) => { + let resolved = + SourcePath::new(SourceFifo("".to_owned(), p.as_bytes().into())); + Ok(Thunk!(move || ctx.state().import_resolved(resolved))) + } } } } diff --git a/crates/jrsonnet-evaluator/src/import.rs b/crates/jrsonnet-evaluator/src/import.rs index 53946968..428257be 100644 --- a/crates/jrsonnet-evaluator/src/import.rs +++ b/crates/jrsonnet-evaluator/src/import.rs @@ -1,7 +1,8 @@ use std::{ any::Any, + borrow::Cow, env::current_dir, - fs, + fmt, fs, io::{ErrorKind, Read}, path::{Path, PathBuf}, }; @@ -9,12 +10,72 @@ use std::{ use fs::File; use jrsonnet_gcmodule::Trace; use jrsonnet_interner::IBytes; -use jrsonnet_parser::{SourceDirectory, SourceFifo, SourceFile, SourcePath}; +use jrsonnet_parser::{IStr, SourceDirectory, SourceFifo, SourceFile, SourcePath}; use crate::{ bail, error::{ErrorKind::*, Result}, }; +#[derive(Clone, Debug, Trace)] +pub enum ResolvePathOwned { + Str(String), + Path(PathBuf), +} +impl fmt::Display for ResolvePathOwned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ResolvePathOwned::Str(s) => write!(f, "{s}"), + ResolvePathOwned::Path(p) => write!(f, "{}", p.display()), + } + } +} +#[derive(Clone, Copy)] +pub enum ResolvePath<'s> { + Str(&'s str), + Path(&'s Path), +} +impl ResolvePath<'_> { + fn to_owned(self) -> ResolvePathOwned { + match self { + ResolvePath::Str(s) => ResolvePathOwned::Str(s.to_owned()), + ResolvePath::Path(p) => ResolvePathOwned::Path(p.to_owned()), + } + } +} +impl AsRef for ResolvePath<'_> { + fn as_ref(&self) -> &Path { + match self { + ResolvePath::Str(s) => s.as_ref(), + ResolvePath::Path(p) => p, + } + } +} +pub trait AsPathLike { + fn as_path(&self) -> ResolvePath<'_>; +} +impl AsPathLike for &T +where + T: AsPathLike + ?Sized, +{ + fn as_path(&self) -> ResolvePath<'_> { + (*self).as_path() + } +} +impl AsPathLike for str { + fn as_path(&self) -> ResolvePath<'_> { + ResolvePath::Str(self) + } +} +impl AsPathLike for IStr { + fn as_path(&self) -> ResolvePath<'_> { + ResolvePath::Str(self) + } +} +impl AsPathLike for Cow<'_, Path> { + fn as_path(&self) -> ResolvePath<'_> { + ResolvePath::Path(self.as_ref()) + } +} /// Implements file resolution logic for `import` and `importStr` pub trait ImportResolver: Trace { @@ -24,16 +85,12 @@ pub trait ImportResolver: Trace { /// /// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value /// may result in panic - fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { - bail!(ImportNotSupported(from.clone(), path.into())) + fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result { + bail!(ImportNotSupported(from.clone(), path.as_path().to_owned())) } - fn resolve_from_default(&self, path: &str) -> Result { + fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result { self.resolve_from(&SourcePath::default(), path) } - /// Resolves absolute path, doesn't supports jpath and other fancy things - fn resolve(&self, path: &Path) -> Result { - bail!(AbsoluteImportNotSupported(path.to_owned())) - } /// Load resolved file /// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`], @@ -117,7 +174,8 @@ fn check_path(path: &Path) -> Result> { } impl ImportResolver for FileImportResolver { - fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { + fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result { + let path = path.as_path(); let mut direct = if let Some(f) = from.downcast_ref::() { let mut o = f.path().to_owned(); o.pop(); @@ -143,12 +201,6 @@ impl ImportResolver for FileImportResolver { } bail!(ImportFileNotFound(from.clone(), path.to_owned())) } - fn resolve(&self, path: &Path) -> Result { - let Some(source) = check_path(path)? else { - bail!(AbsoluteImportFileNotFound(path.to_owned())) - }; - Ok(source) - } fn load_file_contents(&self, id: &SourcePath) -> Result> { let path = if let Some(f) = id.downcast_ref::() { @@ -167,7 +219,7 @@ impl ImportResolver for FileImportResolver { Ok(out) } - fn resolve_from_default(&self, path: &str) -> Result { + fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result { self.resolve_from(&SourcePath::default(), path) } diff --git a/crates/jrsonnet-evaluator/src/lib.rs b/crates/jrsonnet-evaluator/src/lib.rs index a543c93a..de28df3e 100644 --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -29,7 +29,6 @@ use std::{ any::Any, cell::{RefCell, RefMut}, fmt::{self, Debug}, - path::Path, }; pub use ctx::*; @@ -347,12 +346,12 @@ impl State { } /// Has same semantics as `import 'path'` called from `from` file - pub fn import_from(&self, from: &SourcePath, path: &str) -> Result { - let resolved = self.resolve_from(from, path)?; + pub fn import_from(&self, from: &SourcePath, path: impl AsPathLike) -> Result { + let resolved = self.resolve_from(from, &path)?; self.import_resolved(resolved) } - pub fn import(&self, path: impl AsRef) -> Result { - let resolved = self.resolve(path)?; + pub fn import(&self, path: impl AsPathLike) -> Result { + let resolved = self.resolve_from_default(&path)?; self.import_resolved(resolved) } @@ -466,14 +465,12 @@ impl State { impl State { // Only panics in case of [`ImportResolver`] contract violation #[allow(clippy::missing_panics_doc)] - pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { - self.import_resolver().resolve_from(from, path.as_ref()) + pub fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result { + self.import_resolver().resolve_from(from, path) } - - // Only panics in case of [`ImportResolver`] contract violation #[allow(clippy::missing_panics_doc)] - pub fn resolve(&self, path: impl AsRef) -> Result { - self.import_resolver().resolve(path.as_ref()) + pub fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result { + self.import_resolver().resolve_from_default(path) } pub fn import_resolver(&self) -> &dyn ImportResolver { &*self.0.import_resolver diff --git a/crates/jrsonnet-stdlib/src/lib.rs b/crates/jrsonnet-stdlib/src/lib.rs index de84777f..f8021eb9 100644 --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -11,7 +11,7 @@ pub use compat::*; pub use encoding::*; pub use hash::*; use jrsonnet_evaluator::{ - error::{ErrorKind::*, Result}, + error::Result, function::{CallLocation, FuncVal, TlaArg}, trace::PathResolver, ContextBuilder, IStr, ObjValue, ObjValueBuilder, Thunk, Val, @@ -362,23 +362,11 @@ impl ContextInitializer { .ext_vars .insert(name, TlaArg::String(value)); } - pub fn add_ext_code(&self, name: &str, code: impl Into) -> Result<()> { - let code = code.into(); - let source = extvar_source(name, code.clone()); - let parsed = jrsonnet_parser::parse( - &code, - &jrsonnet_parser::ParserSettings { - source: source.clone(), - }, - ) - .map_err(|e| ImportSyntaxError { - path: source, - error: Box::new(e), - })?; + pub fn add_ext_code(&self, name: &str, code: impl AsRef) -> Result<()> { // self.data_mut().volatile_files.insert(source_name, code); self.settings_mut() .ext_vars - .insert(name.into(), TlaArg::Code(parsed)); + .insert(name.into(), TlaArg::InlineCode(code.as_ref().to_owned())); Ok(()) } pub fn add_native(&self, name: impl Into, cb: impl Into) {