From 96da6f397b93e83693defdc6b01d2bb7a43922e3 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 26 May 2022 18:05:31 +0000 Subject: [PATCH] refactor: rework layout and path handling --- --- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -10,7 +10,6 @@ os::raw::{c_char, c_int}, path::{Path, PathBuf}, ptr::null_mut, - rc::Rc, }; use jrsonnet_evaluator::{ @@ -33,7 +32,7 @@ out: RefCell>>, } impl ImportResolver for CallbackImportResolver { - fn resolve_file(&self, from: &Path, path: &Path) -> Result> { + fn resolve_file(&self, from: &Path, path: &Path) -> Result { let base = CString::new(from.to_str().unwrap()).unwrap().into_raw(); let rel = CString::new(path.to_str().unwrap()).unwrap().into_raw(); let found_here: *mut c_char = null_mut(); @@ -73,7 +72,7 @@ unsafe { CString::from_raw(result_ptr) }; } - Ok(found_here_buf.into()) + Ok(found_here_buf) } fn load_file_contents(&self, resolved: &Path) -> Result> { Ok(self.out.borrow().get(resolved).unwrap().clone()) @@ -109,7 +108,7 @@ } } impl ImportResolver for NativeImportResolver { - fn resolve_file(&self, from: &Path, path: &Path) -> Result> { + fn resolve_file(&self, from: &Path, path: &Path) -> Result { let mut new_path = from.to_owned(); new_path.push(path); if new_path.exists() { --- a/bindings/jsonnet/src/lib.rs +++ b/bindings/jsonnet/src/lib.rs @@ -111,7 +111,7 @@ ) -> *const c_char { let filename = CStr::from_ptr(filename); match vm - .evaluate_file_raw_nocwd(&PathBuf::from(filename.to_str().unwrap())) + .import(PathBuf::from(filename.to_str().unwrap())) .and_then(|v| vm.with_tla(v)) .and_then(|v| vm.manifest(v)) { @@ -140,8 +140,8 @@ let filename = CStr::from_ptr(filename); let snippet = CStr::from_ptr(snippet); match vm - .evaluate_snippet_raw( - PathBuf::from(filename.to_str().unwrap()).into(), + .evaluate_snippet( + filename.to_str().unwrap().into(), snippet.to_str().unwrap().into(), ) .and_then(|v| vm.with_tla(v)) @@ -185,7 +185,7 @@ ) -> *const c_char { let filename = CStr::from_ptr(filename); match vm - .evaluate_file_raw_nocwd(&PathBuf::from(filename.to_str().unwrap())) + .import(PathBuf::from(filename.to_str().unwrap())) .and_then(|v| vm.with_tla(v)) .and_then(|v| vm.manifest_multi(v)) { @@ -212,8 +212,8 @@ let filename = CStr::from_ptr(filename); let snippet = CStr::from_ptr(snippet); match vm - .evaluate_snippet_raw( - PathBuf::from(filename.to_str().unwrap()).into(), + .evaluate_snippet( + filename.to_str().unwrap().into(), snippet.to_str().unwrap().into(), ) .and_then(|v| vm.with_tla(v)) @@ -255,7 +255,7 @@ ) -> *const c_char { let filename = CStr::from_ptr(filename); match vm - .evaluate_file_raw_nocwd(&PathBuf::from(filename.to_str().unwrap())) + .import(PathBuf::from(filename.to_str().unwrap())) .and_then(|v| vm.with_tla(v)) .and_then(|v| vm.manifest_stream(v)) { @@ -282,8 +282,8 @@ let filename = CStr::from_ptr(filename); let snippet = CStr::from_ptr(snippet); match vm - .evaluate_snippet_raw( - PathBuf::from(filename.to_str().unwrap()).into(), + .evaluate_snippet( + filename.to_str().unwrap().into(), snippet.to_str().unwrap().into(), ) .and_then(|v| vm.with_tla(v)) --- a/bindings/jsonnet/src/native.rs +++ b/bindings/jsonnet/src/native.rs @@ -1,8 +1,6 @@ use std::{ ffi::{c_void, CStr}, os::raw::{c_char, c_int}, - path::Path, - rc::Rc, }; use gcmodule::Cc; @@ -28,7 +26,7 @@ cb: JsonnetNativeCallback, } impl NativeCallbackHandler for JsonnetNativeCallbackHandler { - fn call(&self, s: State, _from: Option>, args: &[Val]) -> Result { + fn call(&self, s: State, args: &[Val]) -> Result { let mut n_args = Vec::new(); for a in args { n_args.push(Some(Box::new(a.clone()))); --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -120,17 +120,14 @@ opts.manifest.configure(s)?; let val = if opts.input.exec { - s.evaluate_snippet_raw( - PathBuf::from("").into(), - (&opts.input.input as &str).into(), - )? + s.evaluate_snippet("".to_owned(), (&opts.input.input as &str).into())? } else if opts.input.input == "-" { let mut input = Vec::new(); std::io::stdin().read_to_end(&mut input)?; let input_str = std::str::from_utf8(&input)?.into(); - s.evaluate_snippet_raw(PathBuf::from("").into(), input_str)? + s.evaluate_snippet("".to_owned(), input_str)? } else { - s.evaluate_file_raw(&PathBuf::from(opts.input.input))? + s.import(s.resolve_file(&PathBuf::new(), &opts.input.input)?)? }; let val = s.with_tla(val)?; --- a/crates/jrsonnet-cli/src/ext.rs +++ b/crates/jrsonnet-cli/src/ext.rs @@ -108,10 +108,10 @@ s.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into()); } for ext in self.ext_code.iter() { - s.add_ext_code((&ext.name as &str).into(), (&ext.value as &str).into())?; + s.add_ext_code(&ext.name as &str, (&ext.value as &str).into())?; } for ext in self.ext_code_file.iter() { - s.add_ext_code((&ext.name as &str).into(), (&ext.value as &str).into())?; + s.add_ext_code(&ext.name as &str, (&ext.value as &str).into())?; } Ok(()) } --- a/crates/jrsonnet-cli/src/tla.rs +++ b/crates/jrsonnet-cli/src/tla.rs @@ -55,10 +55,10 @@ s.add_tla_str((&tla.name as &str).into(), (&tla.value as &str).into()) } for tla in self.tla_code.iter() { - s.add_tla_code((&tla.name as &str).into(), (&tla.value as &str).into())?; + s.add_tla_code((&tla.name as &str).into(), &tla.value as &str)?; } for tla in self.tla_code_file.iter() { - s.add_tla_code((&tla.name as &str).into(), (&tla.value as &str).into())?; + s.add_tla_code((&tla.name as &str).into(), &tla.value as &str)?; } Ok(()) } --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -28,6 +28,8 @@ jrsonnet-types = { path = "../jrsonnet-types", version = "0.4.2" } jrsonnet-macros = { path = "../jrsonnet-macros", version = "0.4.2" } pathdiff = "0.2.1" +hashbrown = "0.12.1" +static_assertions = "1.1.0" md5 = "0.7.0" base64 = "0.13.0" --- a/crates/jrsonnet-evaluator/build.rs +++ b/crates/jrsonnet-evaluator/build.rs @@ -1,19 +1,14 @@ -use std::{ - env, - fs::File, - io::Write, - path::{Path, PathBuf}, -}; +use std::{borrow::Cow, env, fs::File, io::Write, path::Path}; use bincode::serialize; -use jrsonnet_parser::{parse, ParserSettings}; +use jrsonnet_parser::{parse, ParserSettings, Source}; use jrsonnet_stdlib::STDLIB_STR; fn main() { let parsed = parse( STDLIB_STR, &ParserSettings { - file_name: PathBuf::from("std.jsonnet").into(), + file_name: Source::new_virtual(Cow::Borrowed("")), }, ) .expect("parse"); --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -1,12 +1,8 @@ -use std::{ - fmt::Debug, - path::{Path, PathBuf}, - rc::Rc, -}; +use std::{fmt::Debug, path::PathBuf}; use gcmodule::Trace; use jrsonnet_interner::IStr; -use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType}; +use jrsonnet_parser::{BinaryOpType, ExprLocation, Source, UnaryOpType}; use jrsonnet_types::ValType; use thiserror::Error; @@ -85,7 +81,7 @@ StandaloneSuper, #[error("can't resolve {1} from {0}")] - ImportFileNotFound(PathBuf, PathBuf), + ImportFileNotFound(PathBuf, String), #[error("resolved file not found: {0}")] ResolvedFileNotFound(PathBuf), #[error("imported file is not valid utf-8: {0:?}")] @@ -94,6 +90,8 @@ ImportIo(String), #[error("tried to import {1} from {0}, but imports is not supported")] ImportNotSupported(PathBuf, PathBuf), + #[error("can't import from virtual file")] + CantImportFromVirtualFile, #[error( "syntax error: expected {}, got {:?}", .error.expected, @@ -101,8 +99,7 @@ .map_or_else(|| "EOF".into(), |c| c.to_string()) )] ImportSyntaxError { - #[skip_trace] - path: Rc, + path: Source, source_code: IStr, #[skip_trace] error: Box, --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -452,7 +452,7 @@ Ok(Some(v)) => Ok(v), Ok(None) => throw!(NoSuchField(key.clone())), Err(e) if matches!(e.error(), MagicThisFileUsed) => { - Ok(Val::Str(loc.0.to_string_lossy().into())) + Ok(Val::Str(loc.0.full_path().into())) } Err(e) => Err(e), }, @@ -617,28 +617,27 @@ std_slice(indexable.into_indexable()?, start, end, step)? } - Import(path) => { + i @ (Import(path) | ImportStr(path) | ImportBin(path)) => { let tmp = loc.clone().0; - let mut import_location = tmp.to_path_buf(); - import_location.pop(); - s.push( + let import_location = tmp + .path() + .map(|p| { + let mut p = p.to_owned(); + p.pop(); + p + }) + .unwrap_or_default(); + let path = s.resolve_file(&import_location, path as &str)?; + match i { + Import(_) => s.push( CallLocation::new(loc), - || format!("import {:?}", path), - || s.import_file(&import_location, path), - )? - } - ImportStr(path) => { - let tmp = loc.clone().0; - let mut import_location = tmp.to_path_buf(); - import_location.pop(); - Val::Str(s.import_file_str(&import_location, path)?) + || format!("import {:?}", path.clone()), + || s.import(path.clone()), + )?, + ImportStr(_) => Val::Str(s.import_str(path)?), + ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_bin(path)?)), + _ => unreachable!(), } - ImportBin(path) => { - let tmp = loc.clone().0; - let mut import_location = tmp.to_path_buf(); - import_location.pop(); - let bytes = s.import_file_bin(&import_location, path)?; - Val::Arr(ArrValue::Bytes(bytes)) } }) } --- a/crates/jrsonnet-evaluator/src/function/arglike.rs +++ b/crates/jrsonnet-evaluator/src/function/arglike.rs @@ -47,6 +47,7 @@ } } +#[derive(Clone)] pub enum TlaArg { String(IStr), Code(LocExpr), --- a/crates/jrsonnet-evaluator/src/function/builtin.rs +++ b/crates/jrsonnet-evaluator/src/function/builtin.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, path::Path, rc::Rc}; +use std::borrow::Cow; use gcmodule::Trace; @@ -51,16 +51,16 @@ &self.params } - fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result { + fn call(&self, s: State, ctx: Context, _loc: CallLocation, args: &dyn ArgsLike) -> Result { let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?; let mut out_args = Vec::with_capacity(self.params.len()); for p in &self.params { out_args.push(args[&p.name].evaluate(s.clone())?); } - self.handler.call(s, loc.0.map(|l| l.0.clone()), &out_args) + self.handler.call(s, &out_args) } } pub trait NativeCallbackHandler: Trace { - fn call(&self, s: State, from: Option>, args: &[Val]) -> Result; + fn call(&self, s: State, args: &[Val]) -> Result; } --- a/crates/jrsonnet-evaluator/src/gc.rs +++ b/crates/jrsonnet-evaluator/src/gc.rs @@ -1,13 +1,14 @@ /// Macros to help deal with Gc use std::{ borrow::{Borrow, BorrowMut}, - collections::{HashMap, HashSet}, + collections::HashSet, hash::BuildHasherDefault, ops::{Deref, DerefMut}, }; use gcmodule::{Trace, Tracer}; -use rustc_hash::{FxHashMap, FxHashSet}; +use hashbrown::HashMap; +use rustc_hash::{FxHashSet, FxHasher}; /// Replacement for box, which assumes that the underlying type is [`Trace`] /// Used in places, where `Cc` should be used instead, but it can't, because `CoerceUnsiced` is not stable @@ -115,14 +116,13 @@ } } -#[derive(Clone)] -pub struct GcHashMap(pub FxHashMap); +pub struct GcHashMap(pub HashMap>); impl GcHashMap { pub fn new() -> Self { Self(HashMap::default()) } pub fn with_capacity(capacity: usize) -> Self { - Self(FxHashMap::with_capacity_and_hasher( + Self(HashMap::with_capacity_and_hasher( capacity, BuildHasherDefault::default(), )) @@ -141,7 +141,7 @@ } } impl Deref for GcHashMap { - type Target = FxHashMap; + type Target = HashMap>; fn deref(&self) -> &Self::Target { &self.0 --- a/crates/jrsonnet-evaluator/src/import.rs +++ b/crates/jrsonnet-evaluator/src/import.rs @@ -1,14 +1,11 @@ use std::{ any::Any, - convert::TryFrom, fs, io::Read, path::{Path, PathBuf}, - rc::Rc, }; use fs::File; -use jrsonnet_interner::IStr; use crate::{ error::{Error::*, Result}, @@ -20,21 +17,10 @@ /// Resolves real file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond /// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet` /// where `${vendor}` is a library path. - fn resolve_file(&self, from: &Path, path: &Path) -> Result>; + fn resolve_file(&self, from: &Path, path: &str) -> Result; fn load_file_contents(&self, resolved: &Path) -> Result>; - /// Reads file from filesystem, should be used only with path received from `resolve_file` - fn load_file_str(&self, resolved: &Path) -> Result { - Ok(IStr::try_from(&self.load_file_contents(resolved)? as &[u8]) - .map_err(|_| ImportBadFileUtf8(resolved.to_path_buf()))?) - } - - /// Reads file from filesystem, should be used only with path received from `resolve_file` - fn load_file_bin(&self, resolved: &Path) -> Result> { - Ok(self.load_file_contents(resolved)?.into()) - } - /// # Safety /// /// For use only in bindings, should not be used elsewhere. @@ -46,7 +32,7 @@ /// Dummy resolver, can't resolve/load any file pub struct DummyImportResolver; impl ImportResolver for DummyImportResolver { - fn resolve_file(&self, from: &Path, path: &Path) -> Result> { + fn resolve_file(&self, from: &Path, path: &str) -> Result { throw!(ImportNotSupported(from.into(), path.into())) } @@ -73,22 +59,23 @@ pub library_paths: Vec, } impl ImportResolver for FileImportResolver { - fn resolve_file(&self, from: &Path, path: &Path) -> Result> { + fn resolve_file(&self, from: &Path, path: &str) -> Result { let mut direct = from.to_path_buf(); direct.push(path); if direct.exists() { - Ok(direct.into()) + Ok(direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?) } else { for library_path in &self.library_paths { let mut cloned = library_path.clone(); cloned.push(path); if cloned.exists() { - return Ok(cloned.into()); + return Ok(cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?); } } throw!(ImportFileNotFound(from.to_owned(), path.to_owned())) } } + fn load_file_contents(&self, id: &Path) -> Result> { let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.to_owned()))?; let mut out = Vec::new(); --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -38,6 +38,7 @@ pub mod val; use std::{ + borrow::Cow, cell::{Ref, RefCell, RefMut}, collections::HashMap, fmt::{self, Debug}, @@ -52,7 +53,9 @@ use function::{builtin::Builtin, CallLocation, TlaArg}; use gc::{GcHashMap, TraceBox}; use gcmodule::{Cc, Trace, Weak}; +use hashbrown::hash_map::RawEntryMut; pub use import::*; +use jrsonnet_interner::IBytes; pub use jrsonnet_interner::IStr; pub use jrsonnet_parser as parser; use jrsonnet_parser::*; @@ -96,7 +99,7 @@ /// Limits amount of stack trace items preserved pub max_trace: usize, /// Used for s`td.extVar` - pub ext_vars: HashMap, + pub ext_vars: HashMap, /// Used for ext.native pub ext_natives: HashMap>>, /// TLA vars @@ -141,16 +144,39 @@ stack_generation: usize, breakpoints: Breakpoints, + /// Contains file source codes and evaluation results for imports and pretty-printed stacktraces - files: GcHashMap, FileData>, - str_files: GcHashMap, IStr>, - bin_files: GcHashMap, Rc<[u8]>>, + files: GcHashMap, + /// Contains tla arguments and others, which aren't needed to be obtained by name + volatile_files: GcHashMap, } +struct FileData { + string: Option, + bytes: Option, + parsed: Option, + evaluated: Option, -pub struct FileData { - source_code: IStr, - parsed: LocExpr, - evaluated: Option, + evaluating: bool, +} +impl FileData { + fn new_string(data: IStr) -> Self { + Self { + string: Some(data), + bytes: None, + parsed: None, + evaluated: None, + evaluating: false, + } + } + fn new_bytes(data: IBytes) -> Self { + Self { + string: None, + bytes: Some(data), + parsed: None, + evaluated: None, + evaluating: false, + } + } } #[allow(clippy::type_complexity)] @@ -198,61 +224,159 @@ pub struct State(Rc); impl State { - /// Parses and adds file as loaded - pub fn add_file(&self, path: Rc, source_code: IStr) -> Result { - let parsed = parse( - &source_code, - &ParserSettings { - file_name: path.clone(), - }, - ) - .map_err(|error| ImportSyntaxError { - error: Box::new(error), - path: path.clone(), - source_code: source_code.clone(), - })?; - self.add_parsed_file(path, source_code, parsed.clone())?; + pub fn import_str(&self, path: PathBuf) -> Result { + let mut data = self.data_mut(); + let mut file = data.files.raw_entry_mut().from_key(&path); - Ok(parsed) + let file = match file { + RawEntryMut::Occupied(ref mut d) => d.get_mut(), + RawEntryMut::Vacant(v) => { + let data = self.settings().import_resolver.load_file_contents(&path)?; + v.insert( + path.clone(), + FileData::new_string( + std::str::from_utf8(&data) + .map_err(|_| ImportBadFileUtf8(path.clone()))? + .into(), + ), + ) + .1 + } + }; + if let Some(str) = &file.string { + return Ok(str.clone()); + } + if file.string.is_none() { + file.string = Some( + file.bytes + .as_ref() + .expect("either string or bytes should be set") + .clone() + .cast_str() + .ok_or_else(|| ImportBadFileUtf8(path.clone()))?, + ); + } + Ok(file.string.as_ref().expect("just set").clone()) } + pub fn import_bin(&self, path: PathBuf) -> Result { + let mut data = self.data_mut(); + let mut file = data.files.raw_entry_mut().from_key(&path); - pub fn reset_evaluation_state(&self, name: &Path) { - self.data_mut() - .files - .get_mut(name) - .expect("file not found") - .evaluated - .take(); + let file = match file { + RawEntryMut::Occupied(ref mut d) => d.get_mut(), + RawEntryMut::Vacant(v) => { + let data = self.settings().import_resolver.load_file_contents(&path)?; + v.insert(path.clone(), FileData::new_bytes(data.as_slice().into())) + .1 + } + }; + if let Some(str) = &file.bytes { + return Ok(str.clone()); + } + if file.bytes.is_none() { + file.bytes = Some( + file.string + .as_ref() + .expect("either string or bytes should be set") + .clone() + .cast_bytes(), + ); + } + Ok(file.bytes.as_ref().expect("just set").clone()) } + pub fn import(&self, path: PathBuf) -> Result { + let mut data = self.data_mut(); + let mut file = data.files.raw_entry_mut().from_key(&path); - /// Adds file by source code and parsed expr - pub fn add_parsed_file( - &self, - name: Rc, - source_code: IStr, - parsed: LocExpr, - ) -> Result<()> { - self.data_mut().files.insert( - name, - FileData { - source_code, - parsed, - evaluated: None, - }, - ); + let file = match file { + RawEntryMut::Occupied(ref mut d) => d.get_mut(), + RawEntryMut::Vacant(v) => { + let data = self.settings().import_resolver.load_file_contents(&path)?; + v.insert( + path.clone(), + FileData::new_string( + std::str::from_utf8(&data) + .map_err(|_| ImportBadFileUtf8(path.clone()))? + .into(), + ), + ) + .1 + } + }; + if let Some(val) = &file.evaluated { + return Ok(val.clone()); + } + if file.string.is_none() { + file.string = Some( + std::str::from_utf8( + file.bytes + .as_ref() + .expect("either string or bytes should be set"), + ) + .map_err(|_| ImportBadFileUtf8(path.clone()))? + .into(), + ); + } + let code = file.string.as_ref().expect("just set"); + let file_name = Source::new(path.clone()).expect("resolver should return correct name"); + if file.parsed.is_none() { + file.parsed = Some( + jrsonnet_parser::parse( + code, + &ParserSettings { + file_name: file_name.clone(), + }, + ) + .map_err(|e| ImportSyntaxError { + path: file_name, + source_code: code.clone(), + error: Box::new(e), + })?, + ); + } + let parsed = file.parsed.as_ref().expect("just set").clone(); + if file.evaluating { + throw!(InfiniteRecursionDetected) + } + file.evaluating = true; + // Dropping file here, as it borrows data, which may be used in evaluation + drop(data); + let res = evaluate(self.clone(), self.create_default_context(), &parsed); + + let mut data = self.data_mut(); + let mut file = data.files.raw_entry_mut().from_key(&path); - Ok(()) + let file = match file { + RawEntryMut::Occupied(ref mut d) => d.get_mut(), + RawEntryMut::Vacant(_) => unreachable!("this file was just here!"), + }; + file.evaluating = false; + match res { + Ok(v) => { + file.evaluated = Some(v.clone()); + Ok(v) + } + Err(e) => Err(e), + } } - pub fn get_source(&self, name: &Path) -> Option { - let ro_map = &self.data().files; - ro_map.get(name).map(|value| value.source_code.clone()) + + pub fn get_source(&self, name: Source) -> Option { + let data = self.data(); + match name.repr() { + Ok(real) => data + .files + .get(real) + .and_then(|f| f.string.as_ref()) + .map(ToString::to_string), + Err(e) => data.volatile_files.get(e).map(ToOwned::to_owned), + } } - pub fn map_source_locations(&self, file: &Path, locs: &[usize]) -> Vec { + pub fn map_source_locations(&self, file: Source, locs: &[u32]) -> Vec { offset_to_location(&self.get_source(file).unwrap_or_else(|| "".into()), locs) } pub fn map_from_source_location( &self, - file: &Path, + file: Source, line: usize, column: usize, ) -> Option { @@ -262,74 +386,14 @@ column, ) } - pub fn import_file(&self, from: &Path, path: &Path) -> Result { - let file_path = self.resolve_file(from, path)?; - { - let data = self.data(); - let files = &data.files; - if files.contains_key(&file_path as &Path) { - drop(data); - return self.evaluate_loaded_file_raw(&file_path); - } - } - let contents = self.load_file_str(&file_path)?; - self.add_file(file_path.clone(), contents)?; - self.evaluate_loaded_file_raw(&file_path) - } - pub(crate) fn import_file_str(&self, from: &Path, path: &Path) -> Result { - let path = self.resolve_file(from, path)?; - if !self.data().str_files.contains_key(&path) { - let file_str = self.load_file_str(&path)?; - self.data_mut().str_files.insert(path.clone(), file_str); - } - Ok(self.data().str_files.get(&path).cloned().unwrap()) - } - pub(crate) fn import_file_bin(&self, from: &Path, path: &Path) -> Result> { - let path = self.resolve_file(from, path)?; - if !self.data().bin_files.contains_key(&path) { - let file_bin = self.load_file_bin(&path)?; - self.data_mut().bin_files.insert(path.clone(), file_bin); - } - Ok(self.data().bin_files.get(&path).cloned().unwrap()) - } - - fn evaluate_loaded_file_raw(&self, name: &Path) -> Result { - let expr: LocExpr = { - let ro_map = &self.data().files; - let value = ro_map - .get(name) - .unwrap_or_else(|| panic!("file not added: {:?}", name)); - if let Some(ref evaluated) = value.evaluated { - return Ok(evaluated.clone()); - } - value.parsed.clone() - }; - let value = evaluate(self.clone(), self.create_default_context(), &expr)?; - { - self.data_mut() - .files - .get_mut(name) - .unwrap() - .evaluated - .replace(value.clone()); - } - Ok(value) - } - /// Adds standard library global variable (std) to this evaluator pub fn with_stdlib(&self) -> &Self { - use jrsonnet_stdlib::STDLIB_STR; - let std_path: Rc = PathBuf::from("std.jsonnet").into(); - - self.add_parsed_file( - std_path.clone(), - STDLIB_STR.to_owned().into(), - stdlib::get_parsed_stdlib(), + let val = evaluate( + self.clone(), + self.create_default_context(), + &stdlib::get_parsed_stdlib(), ) - .expect("stdlib is correct"); - let val = self - .evaluate_loaded_file_raw(&std_path) - .expect("stdlib is correct"); + .expect("std should not fail"); self.settings_mut().globals.insert("std".into(), val); self } @@ -506,46 +570,55 @@ /// Raw methods evaluate passed values but don't perform TLA execution impl State { - pub fn evaluate_file_raw(&self, name: &Path) -> Result { - self.import_file(&std::env::current_dir().expect("cwd"), name) - } - pub fn evaluate_file_raw_nocwd(&self, name: &Path) -> Result { - self.import_file(&PathBuf::from("."), name) - } /// Parses and evaluates the given snippet - pub fn evaluate_snippet_raw(&self, source: Rc, code: IStr) -> Result { - let parsed = parse( + pub fn evaluate_snippet(&self, name: String, code: String) -> Result { + let source = Source::new_virtual(Cow::Owned(name.clone())); + let parsed = jrsonnet_parser::parse( &code, &ParserSettings { file_name: source.clone(), }, ) .map_err(|e| ImportSyntaxError { - path: source.clone(), - source_code: code.clone(), + path: source, + source_code: code.clone().into(), error: Box::new(e), })?; - self.add_parsed_file(source, code, parsed.clone())?; - self.evaluate_expr_raw(parsed) + self.data_mut().volatile_files.insert(name, code); + evaluate(self.clone(), self.create_default_context(), &parsed) } - /// Evaluates the parsed expression - pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result { - evaluate(self.clone(), self.create_default_context(), &code) - } } /// Settings utilities impl State { pub fn add_ext_var(&self, name: IStr, value: Val) { - self.settings_mut().ext_vars.insert(name, value); + self.settings_mut() + .ext_vars + .insert(name, TlaArg::Val(value)); } pub fn add_ext_str(&self, name: IStr, value: IStr) { - self.add_ext_var(name, Val::Str(value)); + self.settings_mut() + .ext_vars + .insert(name, TlaArg::String(value)); } - pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> { - let value = - self.evaluate_snippet_raw(PathBuf::from(format!("ext_code {}", name)).into(), code)?; - self.add_ext_var(name, value); + pub fn add_ext_code(&self, name: &str, code: String) -> Result<()> { + let source_name = format!("", name); + let source = Source::new_virtual(Cow::Owned(source_name.clone())); + let parsed = jrsonnet_parser::parse( + &code, + &ParserSettings { + file_name: source.clone(), + }, + ) + .map_err(|e| ImportSyntaxError { + path: source, + source_code: code.clone().into(), + error: Box::new(e), + })?; + self.data_mut().volatile_files.insert(source_name, code); + self.settings_mut() + .ext_vars + .insert(name.into(), TlaArg::Code(parsed)); Ok(()) } @@ -559,22 +632,33 @@ .tla_vars .insert(name, TlaArg::String(value)); } - pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> { - let parsed = self.add_file(PathBuf::from(format!("tla_code {}", name)).into(), code)?; + pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> { + let source_name = format!("", name); + let source = Source::new_virtual(Cow::Owned(source_name.clone())); + let parsed = jrsonnet_parser::parse( + code, + &ParserSettings { + file_name: source.clone(), + }, + ) + .map_err(|e| ImportSyntaxError { + path: source, + source_code: code.into(), + error: Box::new(e), + })?; + self.data_mut() + .volatile_files + .insert(source_name, code.to_owned()); self.settings_mut() .tla_vars .insert(name, TlaArg::Code(parsed)); Ok(()) } - pub fn resolve_file(&self, from: &Path, path: &Path) -> Result> { - self.settings().import_resolver.resolve_file(from, path) - } - pub fn load_file_str(&self, path: &Path) -> Result { - self.settings().import_resolver.load_file_str(path) - } - pub fn load_file_bin(&self, path: &Path) -> Result> { - self.settings().import_resolver.load_file_bin(path) + pub fn resolve_file(&self, from: &Path, path: &str) -> Result { + self.settings() + .import_resolver + .resolve_file(from, path.as_ref()) } pub fn import_resolver(&self) -> Ref { --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -14,6 +14,15 @@ pub struct LayeredHashMap(Cc); impl LayeredHashMap { + pub fn iter_keys(self, mut handler: impl FnMut(IStr)) { + for (k, _) in self.0.current.iter() { + handler(k.clone()); + } + if let Some(parent) = self.0.parent.clone() { + parent.iter_keys(handler); + } + } + pub fn extend(self, new_layer: GcHashMap>) -> Self { Self(Cc::new(LayeredHashMapInternals { parent: Some(self), --- a/crates/jrsonnet-evaluator/src/stdlib/expr.rs +++ b/crates/jrsonnet-evaluator/src/stdlib/expr.rs @@ -1,6 +1,6 @@ -use std::path::PathBuf; +use std::borrow::Cow; -use jrsonnet_parser::{LocExpr, ParserSettings}; +use jrsonnet_parser::{LocExpr, ParserSettings, Source}; thread_local! { /// To avoid parsing again when issued from the same thread @@ -16,7 +16,7 @@ jrsonnet_parser::parse( jrsonnet_stdlib::STDLIB_STR, &ParserSettings { - file_name: PathBuf::from("std.jsonnet").into(), + file_name: Source::new_virtual(Cow::Borrowed("")), }, ) .unwrap() --- a/crates/jrsonnet-evaluator/src/stdlib/mod.rs +++ b/crates/jrsonnet-evaluator/src/stdlib/mod.rs @@ -5,17 +5,17 @@ use format::{format_arr, format_obj}; use gcmodule::Cc; -use jrsonnet_interner::IStr; +use jrsonnet_interner::{IBytes, IStr}; use serde::Deserialize; use serde_yaml::DeserializingQuirks; use crate::{ error::{Error::*, Result}, - function::{builtin::StaticBuiltin, CallLocation, FuncVal}, + function::{builtin::StaticBuiltin, ArgLike, CallLocation, FuncVal}, operator::evaluate_mod_op, stdlib::manifest::{manifest_yaml_ex, ManifestYamlOptions}, throw, - typed::{Any, BoundedUsize, Bytes, Either2, Either4, PositiveF64, Typed, VecVal, M1}, + typed::{Any, BoundedUsize, Either2, Either4, PositiveF64, Typed, VecVal, M1}, val::{equals, primitive_equals, ArrValue, IndexableVal, Slice}, Either, ObjValue, State, Val, }; @@ -365,12 +365,16 @@ #[jrsonnet_macros::builtin] fn builtin_ext_var(s: State, x: IStr) -> Result { + let ctx = s.create_default_context(); Ok(Any(s + .clone() .settings() .ext_vars .get(&x) .cloned() - .ok_or(UndefinedExternalVariable(x))?)) + .ok_or(UndefinedExternalVariable(x))? + .evaluate_arg(s.clone(), ctx, true)? + .evaluate(s)?)) } #[jrsonnet_macros::builtin] @@ -489,15 +493,15 @@ } #[jrsonnet_macros::builtin] -fn builtin_encode_utf8(str: IStr) -> Result { - Ok(Bytes(str.bytes().collect::>().into())) +fn builtin_encode_utf8(str: IStr) -> Result { + Ok(str.cast_bytes()) } #[jrsonnet_macros::builtin] -fn builtin_decode_utf8(arr: Bytes) -> Result { - Ok(std::str::from_utf8(&arr.0) - .map_err(|_| RuntimeError("bad utf8".into()))? - .into()) +fn builtin_decode_utf8(arr: IBytes) -> Result { + Ok(arr + .cast_str() + .ok_or_else(|| RuntimeError("bad utf8".into()))?) } #[jrsonnet_macros::builtin] @@ -509,33 +513,28 @@ fn builtin_trace(s: State, loc: CallLocation, str: IStr, rest: Any) -> Result { eprint!("TRACE:"); if let Some(loc) = loc.0 { - let locs = s.map_source_locations(&loc.0, &[loc.1]); - eprint!( - " {}:{}", - loc.0.file_name().unwrap().to_str().unwrap(), - locs[0].line - ); + let locs = s.map_source_locations(loc.0.clone(), &[loc.1]); + eprint!(" {}:{}", loc.0.short_display(), locs[0].line); } eprintln!(" {}", str); Ok(rest) as Result } #[jrsonnet_macros::builtin] -fn builtin_base64(input: Either![Bytes, IStr]) -> Result { +fn builtin_base64(input: Either![IBytes, IStr]) -> Result { use Either2::*; Ok(match input { - A(a) => base64::encode(a.0), + A(a) => base64::encode(a.as_slice()), B(l) => base64::encode(l.bytes().collect::>()), }) } #[jrsonnet_macros::builtin] -fn builtin_base64_decode_bytes(input: IStr) -> Result { - Ok(Bytes( - base64::decode(&input.as_bytes()) - .map_err(|_| RuntimeError("bad base64".into()))? - .into(), - )) +fn builtin_base64_decode_bytes(input: IStr) -> Result { + Ok(base64::decode(&input.as_bytes()) + .map_err(|_| RuntimeError("bad base64".into()))? + .as_slice() + .into()) } #[jrsonnet_macros::builtin] --- a/crates/jrsonnet-evaluator/src/trace/location.rs +++ b/crates/jrsonnet-evaluator/src/trace/location.rs @@ -24,7 +24,7 @@ } #[allow(clippy::module_name_repetitions)] -pub fn offset_to_location(file: &str, offsets: &[usize]) -> Vec { +pub fn offset_to_location(file: &str, offsets: &[u32]) -> Vec { if offsets.is_empty() { return vec![]; } @@ -59,7 +59,7 @@ { column += 1; match offset_map.last() { - Some(x) if x.0 == pos => { + Some(x) if x.0 == pos as u32 => { let out_idx = x.1; with_no_known_line_ending.push(out_idx); out[out_idx].offset = pos; @@ -79,7 +79,7 @@ } this_line_offset = pos + 1; - if pos == max_offset + 1 { + if pos == max_offset as usize + 1 { break; } } --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -2,6 +2,7 @@ use std::path::{Path, PathBuf}; +use jrsonnet_parser::Source; pub use location::*; use crate::{error::Error, LocError, State}; @@ -56,7 +57,7 @@ ) -> Result<(), std::fmt::Error> { if start.line == end.line { if start.column == end.column { - write!(out, "{}:{}", start.line, end.column - 1)?; + write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?; } else { write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?; } @@ -96,7 +97,10 @@ use std::fmt::Write; writeln!(out)?; - let mut n = self.resolver.resolve(path); + let mut n = match path.repr() { + Ok(r) => self.resolver.resolve(r), + Err(v) => v.to_string(), + }; let mut offset = error.location.offset; let is_eof = if offset >= source_code.len() { offset = source_code.len().saturating_sub(1); @@ -104,7 +108,7 @@ } else { false }; - let mut location = offset_to_location(source_code, &[offset]) + let mut location = offset_to_location(source_code, &[offset as u32]) .into_iter() .next() .unwrap(); @@ -125,9 +129,13 @@ use std::fmt::Write; #[allow(clippy::option_if_let_else)] if let Some(location) = location { - let mut resolved_path = self.resolver.resolve(&location.0); + let mut resolved_path = match location.0.repr() { + Ok(r) => self.resolver.resolve(r), + Err(v) => v.to_string(), + }; // TODO: Process all trace elements first - let location = s.map_source_locations(&location.0, &[location.1, location.2]); + let location = + s.map_source_locations(location.0.clone(), &[location.1, location.2]); write!(resolved_path, ":").unwrap(); print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap(); write!(resolved_path, ":").unwrap(); @@ -176,15 +184,16 @@ writeln!(out)?; let desc = &item.desc; if let Some(source) = &item.location { - let start_end = s.map_source_locations(&source.0, &[source.1, source.2]); + let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]); + let resolved_path = match source.0.repr() { + Ok(r) => r.display().to_string(), + Err(v) => v.to_string(), + }; write!( out, " at {} ({}:{}:{})", - desc, - source.0.to_str().unwrap(), - start_end[0].line, - start_end[0].column, + desc, resolved_path, start_end[0].line, start_end[0].column, )?; } else { write!(out, " during {}", desc)?; @@ -216,7 +225,7 @@ { writeln!(out)?; let offset = error.location.offset; - let location = offset_to_location(source_code, &[offset]) + let location = offset_to_location(source_code, &[offset as u32]) .into_iter() .next() .unwrap(); @@ -237,10 +246,10 @@ writeln!(out)?; let desc = &item.desc; if let Some(source) = &item.location { - let start_end = s.map_source_locations(&source.0, &[source.1, source.2]); + let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]); self.print_snippet( out, - &s.get_source(&source.0).unwrap(), + &s.get_source(source.0.clone()).unwrap(), &source.0, &start_end[0], &start_end[1], @@ -259,7 +268,7 @@ &self, out: &mut dyn std::fmt::Write, source: &str, - origin: &Path, + origin: &Source, start: &CodeLocation, end: &CodeLocation, desc: &str, @@ -275,7 +284,10 @@ .take(end.line_end_offset - end.line_start_offset) .collect(); - let origin = self.resolver.resolve(origin); + let origin = match origin.repr() { + Ok(r) => self.resolver.resolve(r), + Err(v) => v.to_string(), + }; let snippet = Snippet { opt: FormatOptions { color: true, --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -1,7 +1,7 @@ -use std::{ops::Deref, rc::Rc}; +use std::ops::Deref; use gcmodule::Cc; -use jrsonnet_interner::IStr; +use jrsonnet_interner::{IBytes, IStr}; pub use jrsonnet_macros::Typed; use jrsonnet_types::{ComplexValType, ValType}; @@ -303,19 +303,17 @@ } /// Specialization -pub struct Bytes(pub Rc<[u8]>); - -impl Typed for Bytes { +impl Typed for IBytes { const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0))); fn into_untyped(value: Self, _: State) -> Result { - Ok(Val::Arr(ArrValue::Bytes(value.0))) + Ok(Val::Arr(ArrValue::Bytes(value))) } fn from_untyped(value: Val, s: State) -> Result { if let Val::Arr(ArrValue::Bytes(bytes)) = value { - return Ok(Self(bytes)); + return Ok(bytes); } ::TYPE.check(s.clone(), &value)?; match value { @@ -325,7 +323,7 @@ let r = e?; out.push(u8::from_untyped(r, s.clone())?); } - Ok(Self(out.into())) + Ok(out.as_slice().into()) } _ => unreachable!(), } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, fmt::Debug, rc::Rc}; use gcmodule::{Cc, Trace}; -use jrsonnet_interner::IStr; +use jrsonnet_interner::{IBytes, IStr}; use jrsonnet_types::ValType; use crate::{ @@ -31,6 +31,7 @@ #[allow(clippy::module_name_repetitions)] #[derive(Clone, Trace)] pub struct Thunk(Cc>>); + impl Thunk where T: Clone + Trace, @@ -186,7 +187,7 @@ #[derive(Debug, Clone, Trace)] #[force_tracking] pub enum ArrValue { - Bytes(#[skip_trace] Rc<[u8]>), + Bytes(#[skip_trace] IBytes), Lazy(Cc>>), Eager(Cc>), Extended(Box<(Self, Self)>), @@ -194,6 +195,10 @@ Slice(Box), Reversed(Box), } + +#[cfg(target_pointer_width = "64")] +static_assertions::assert_eq_size!(ArrValue, [u8; 16]); + impl ArrValue { pub fn new_eager() -> Self { Self::Eager(Cc::new(Vec::new())) @@ -465,6 +470,9 @@ Func(FuncVal), } +#[cfg(target_pointer_width = "64")] +static_assertions::assert_eq_size!(Val, [u8; 32]); + impl Val { pub const fn as_bool(&self) -> Option { match self { --- a/crates/jrsonnet-evaluator/tests/as_native.rs +++ b/crates/jrsonnet-evaluator/tests/as_native.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use jrsonnet_evaluator::{error::Result, State}; mod common; @@ -9,7 +7,7 @@ let s = State::default(); s.with_stdlib(); - let val = s.evaluate_snippet_raw(PathBuf::new().into(), r#"function(a, b) a + b"#.into())?; + let val = s.evaluate_snippet("snip".to_owned(), r#"function(a, b) a + b"#.into())?; let func = val.as_func().expect("this is function"); let native = func.into_native::<((u32, u32), u32)>(); --- a/crates/jrsonnet-evaluator/tests/builtin.rs +++ b/crates/jrsonnet-evaluator/tests/builtin.rs @@ -1,7 +1,5 @@ mod common; -use std::path::PathBuf; - use gcmodule::Cc; use jrsonnet_evaluator::{ error::Result, @@ -27,7 +25,7 @@ CallLocation::native(), &(), )?, - s.clone(), + s, )?; ensure_eq!(v, 1); @@ -48,8 +46,8 @@ Val::Func(FuncVal::StaticBuiltin(native_add::INST)), ); - let v = s.evaluate_snippet_raw( - PathBuf::new().into(), + let v = s.evaluate_snippet( + "snip".to_owned(), " assert nativeAdd(1, 2) == 3; assert nativeAdd(100, 200) == 300; @@ -57,7 +55,7 @@ " .into(), )?; - ensure_val_eq!(s.clone(), v, Val::Null); + ensure_val_eq!(s, v, Val::Null); Ok(()) } @@ -82,8 +80,8 @@ Val::Func(FuncVal::StaticBuiltin(curry_add::INST)), ); - let v = s.evaluate_snippet_raw( - PathBuf::new().into(), + let v = s.evaluate_snippet( + "snip".to_owned(), " local a = curryAdd(1); local b = curryAdd(4); @@ -97,6 +95,6 @@ " .into(), )?; - ensure_val_eq!(s.clone(), v, Val::Null); + ensure_val_eq!(s, v, Val::Null); Ok(()) } --- a/crates/jrsonnet-evaluator/tests/golden.rs +++ b/crates/jrsonnet-evaluator/tests/golden.rs @@ -20,7 +20,7 @@ common::with_test(&s); s.set_import_resolver(Box::new(FileImportResolver::default())); - let v = match s.evaluate_file_raw(file) { + let v = match s.import(file.to_owned()) { Ok(v) => v, Err(e) => return s.stringify_err(&e), }; --- a/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.golden +++ b/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.golden @@ -1,202 +1,2 @@ -stack overflow, try to reduce recursion, or set --max-stack to bigger value - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" - issue23.jsonnet:1:1-26: import "issue23.jsonnet" \ No newline at end of file +infinite recursion detected + issue23.jsonnet:1:1-26: import "/home/lach/build/jrsonnet/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet" \ No newline at end of file --- a/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.golden +++ b/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.golden @@ -1,2 +1,3 @@ -variable is not defined: a +runtime error: variable is not defined: a +There is variable(s) with similar names present: std, test missing_binding.jsonnet:1:1-3: variable access \ No newline at end of file --- a/crates/jrsonnet-evaluator/tests/sanity.rs +++ b/crates/jrsonnet-evaluator/tests/sanity.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val}; mod common; @@ -9,10 +7,10 @@ let s = State::default(); s.with_stdlib(); - let v = s.evaluate_snippet_raw(PathBuf::new().into(), "assert 1 == 1: 'fail'; null".into())?; - ensure_val_eq!(s.clone(), v, Val::Null); - let v = s.evaluate_snippet_raw(PathBuf::new().into(), "std.assertEqual(1, 1)".into())?; - ensure_val_eq!(s.clone(), v, Val::Bool(true)); + let v = s.evaluate_snippet("snip".to_owned(), "assert 1 == 1: 'fail'; null".into())?; + ensure_val_eq!(s, v, Val::Null); + let v = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 1)".into())?; + ensure_val_eq!(s, v, Val::Bool(true)); Ok(()) } @@ -23,9 +21,7 @@ s.with_stdlib(); { - let e = match s - .evaluate_snippet_raw(PathBuf::new().into(), "assert 1 == 2: 'fail'; null".into()) - { + let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null".into()) { Ok(_) => throw_runtime!("assertion should fail"), Err(e) => e, }; @@ -33,8 +29,7 @@ ensure!(e.starts_with("assert failed: fail\n")); } { - let e = match s.evaluate_snippet_raw(PathBuf::new().into(), "std.assertEqual(1, 2)".into()) - { + let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)".into()) { Ok(_) => throw_runtime!("assertion should fail"), Err(e) => e, }; --- a/crates/jrsonnet-evaluator/tests/suite.rs +++ b/crates/jrsonnet-evaluator/tests/suite.rs @@ -20,7 +20,7 @@ common::with_test(&s); s.set_import_resolver(Box::new(FileImportResolver::default())); - match s.evaluate_file_raw(file) { + match s.import(file.to_owned()) { Ok(Val::Bool(true)) => {} Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()), Ok(_) => panic!("test {} returned wrong type as result", file.display()), --- a/crates/jrsonnet-evaluator/tests/typed_obj.rs +++ b/crates/jrsonnet-evaluator/tests/typed_obj.rs @@ -1,6 +1,6 @@ mod common; -use std::{fmt::Debug, path::PathBuf}; +use std::fmt::Debug; use jrsonnet_evaluator::{error::Result, typed::Typed, State}; @@ -25,11 +25,11 @@ let s = State::default(); s.with_stdlib(); let a = A::from_untyped( - s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?, + s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?, s.clone(), )?; ensure_eq!(a, A { a: 1, b: 2 }); - test_roundtrip(a.clone(), s.clone())?; + test_roundtrip(a, s)?; Ok(()) } @@ -45,7 +45,7 @@ let s = State::default(); s.with_stdlib(); let b = B::from_untyped( - s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, c: 2}".into())?, + s.evaluate_snippet("snip".to_owned(), "{a: 1, c: 2}".into())?, s.clone(), )?; ensure_eq!(b, B { a: 1, b: 2 }); @@ -53,7 +53,7 @@ &B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str, r#"{"a": 1, "c": 2}"#, ); - test_roundtrip(b.clone(), s.clone())?; + test_roundtrip(b, s)?; Ok(()) } @@ -77,8 +77,8 @@ let s = State::default(); s.with_stdlib(); let obj = Object::from_untyped( - s.evaluate_snippet_raw( - PathBuf::new().into(), + s.evaluate_snippet( + "snip".to_owned(), "{apiVersion: 'ver', kind: 'kind', b: 2}".into(), )?, s.clone(), @@ -97,7 +97,7 @@ &Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str, r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#, ); - test_roundtrip(obj.clone(), s.clone())?; + test_roundtrip(obj, s)?; Ok(()) } @@ -112,7 +112,7 @@ let s = State::default(); s.with_stdlib(); let c = C::from_untyped( - s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?, + s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?, s.clone(), )?; ensure_eq!(c, C { a: Some(1), b: 2 }); @@ -120,7 +120,7 @@ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str, r#"{"a": 1, "b": 2}"#, ); - test_roundtrip(c.clone(), s.clone())?; + test_roundtrip(c, s)?; Ok(()) } @@ -129,7 +129,7 @@ let s = State::default(); s.with_stdlib(); let c = C::from_untyped( - s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2}".into())?, + s.evaluate_snippet("snip".to_owned(), "{b: 2}".into())?, s.clone(), )?; ensure_eq!(c, C { a: None, b: 2 }); @@ -137,7 +137,7 @@ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str, r#"{"b": 2}"#, ); - test_roundtrip(c.clone(), s.clone())?; + test_roundtrip(c, s)?; Ok(()) } @@ -158,7 +158,7 @@ let s = State::default(); s.with_stdlib(); let d = D::from_untyped( - s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v:1}".into())?, + s.evaluate_snippet("snip".to_owned(), "{b: 2, v:1}".into())?, s.clone(), )?; ensure_eq!( @@ -172,7 +172,7 @@ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str, r#"{"b": 2, "v": 1}"#, ); - test_roundtrip(d.clone(), s.clone())?; + test_roundtrip(d, s)?; Ok(()) } @@ -181,7 +181,7 @@ let s = State::default(); s.with_stdlib(); let d = D::from_untyped( - s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v: '1'}".into())?, + s.evaluate_snippet("snip".to_owned(), "{b: 2, v: '1'}".into())?, s.clone(), )?; ensure_eq!(d, D { e: None, b: 2 }); @@ -189,6 +189,6 @@ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str, r#"{"b": 2}"#, ); - test_roundtrip(d.clone(), s.clone())?; + test_roundtrip(d, s)?; Ok(()) } --- a/crates/jrsonnet-interner/src/lib.rs +++ b/crates/jrsonnet-interner/src/lib.rs @@ -127,6 +127,11 @@ unsafe { Inner::assume_utf8(&self.0) }; IStr(self.0.clone()) } + + #[must_use] + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } } impl Deref for IBytes { --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -362,7 +362,7 @@ #[cfg(test)] pub mod tests { - use std::path::PathBuf; + use std::borrow::Cow; use BinaryOpType::*; @@ -374,7 +374,7 @@ parse( $s, &ParserSettings { - file_name: Source::new(PathBuf::from("test.jsonnet")).unwrap(), + file_name: Source::new_virtual(Cow::Borrowed("")), }, ) .unwrap() @@ -385,11 +385,7 @@ ($expr:expr, $from:expr, $to:expr$(,)?) => { LocExpr( std::rc::Rc::new($expr), - ExprLocation( - Source::new(PathBuf::from("test.jsonnet")).unwrap(), - $from, - $to, - ), + ExprLocation(Source::new_virtual(Cow::Borrowed("")), $from, $to), ) }; } @@ -727,12 +723,10 @@ fn add_location_info_to_all_sub_expressions() { use Expr::*; - let file_name = Source::new(PathBuf::from("test.jsonnet")).unwrap(); + let file_name = Source::new_virtual(Cow::Borrowed("")); let expr = parse( "{} { local x = 1, x: x } + {}", - &ParserSettings { - file_name: file_name.clone(), - }, + &ParserSettings { file_name }, ) .unwrap(); assert_eq!( -- gitstuff