difftreelog
feat add standard like api to evaluator
in: master
1 file changed
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth34 path::PathBuf,34 path::PathBuf,35 rc::Rc,35 rc::Rc,36};36};37use trace::{offset_to_location, CodeLocation};37use trace::{offset_to_location, CodeLocation, CompactFormat, TraceFormat};38pub use val::*;38pub use val::*;393940type BindableFn = dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>;40type BindableFn = dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>;58 }58 }59}59}606061#[derive(Clone)]62pub enum ManifestFormat {63 Yaml(usize),64 Json(usize),65 None,66}6761pub struct EvaluationSettings {68pub struct EvaluationSettings {62 /// Limits recursion by limiting stack frames69 /// Limits recursion by limiting stack frames63 pub max_stack: usize,70 pub max_stack: usize,64 /// Limit amount of stack trace items preserved71 /// Limit amount of stack trace items preserved65 pub max_trace: usize,72 pub max_trace: usize,66 /// Used for std.extVar73 /// Used for std.extVar67 pub ext_vars: HashMap<Rc<str>, Val>,74 pub ext_vars: HashMap<Rc<str>, Val>,75 /// TLA vars76 pub tla_vars: HashMap<Rc<str>, Val>,68 /// Global variables are inserted in default context77 /// Global variables are inserted in default context69 pub globals: HashMap<Rc<str>, Val>,78 pub globals: HashMap<Rc<str>, Val>,70 /// Used to resolve file locations/contents79 /// Used to resolve file locations/contents71 pub import_resolver: Box<dyn ImportResolver>,80 pub import_resolver: Box<dyn ImportResolver>,81 /// Used in manifestification functions82 pub manifest_format: ManifestFormat,83 /// Used for bindings84 pub trace_format: Box<dyn TraceFormat>,72}85}73impl Default for EvaluationSettings {86impl Default for EvaluationSettings {74 fn default() -> Self {87 fn default() -> Self {77 max_trace: 20,90 max_trace: 20,78 globals: Default::default(),91 globals: Default::default(),79 ext_vars: Default::default(),92 ext_vars: Default::default(),93 tla_vars: Default::default(),80 import_resolver: Box::new(DummyImportResolver),94 import_resolver: Box::new(DummyImportResolver),95 manifest_format: ManifestFormat::Json(4),96 trace_format: Box::new(CompactFormat {97 padding: 4,98 resolver: trace::PathResolver::Absolute,99 }),81 }100 }82 }101 }83}102}114 EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))133 EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))115}134}116pub fn create_error(err: Error) -> LocError {135pub fn create_error(err: Error) -> LocError {117 with_state(|s| s.error(err))136 LocError(err, StackTrace(vec![]))118}137}119pub fn create_error_result<T>(err: Error) -> Result<T> {138pub fn create_error_result<T>(err: Error) -> Result<T> {120 Err(with_state(|s| s.error(err)))139 Err(LocError(err, StackTrace(vec![])))121}140}122pub(crate) fn push<T>(141pub(crate) fn push<T>(123 e: &Option<ExprLocation>,142 e: &Option<ExprLocation>,134/// Maintains stack trace and import resolution153/// Maintains stack trace and import resolution135#[derive(Default, Clone)]154#[derive(Default, Clone)]136pub struct EvaluationState(Rc<EvaluationStateInternals>);155pub struct EvaluationState(Rc<EvaluationStateInternals>);156137impl EvaluationState {157impl EvaluationState {138 fn data(&self) -> Ref<EvaluationData> {139 self.0.data.borrow()140 }141 fn data_mut(&self) -> RefMut<EvaluationData> {142 self.0.data.borrow_mut()143 }144 pub fn settings(&self) -> Ref<EvaluationSettings> {145 self.0.settings.borrow()146 }147 pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {148 self.0.settings.borrow_mut()149 }150151 pub fn evaluate_file_to_json(&self, path: &PathBuf) -> std::result::Result<Rc<str>, LocError> {152 self.import_file(&PathBuf::new(), &path)153 .and_then(|v| v.into_json(4))154 }155 pub fn evaluate_snippet_to_json(156 &self,157 path: &PathBuf,158 snippet: &str,159 ) -> std::result::Result<Rc<str>, LocError> {160 self.parse_evaluate_raw(Rc::new(path.clone()), snippet)161 .and_then(|v| v.into_json(4))162 }163164 /// Parses and adds file to loaded158 /// Parses and adds file to loaded165 pub fn add_file(&self, path: Rc<PathBuf>, source_code: Rc<str>) -> Result<()> {159 pub fn add_file(&self, path: Rc<PathBuf>, source_code: Rc<str>) -> Result<()> {166 self.add_parsed_file(160 self.add_parsed_file(211 offset_to_location(&self.get_source(file).unwrap(), locs)205 offset_to_location(&self.get_source(file).unwrap(), locs)212 }206 }213207214 pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {215 self.run_in_state(|| {216 let expr: LocExpr = {217 let ro_map = &self.data().files;218 let value = ro_map219 .get(name)220 .unwrap_or_else(|| panic!("file not added: {:?}", name));221 if value.evaluated.is_some() {222 return Ok(value.evaluated.clone().unwrap());223 }224 value.parsed.clone()225 };226 let value = evaluate(self.create_default_context()?, &expr)?;227 {228 self.0229 .data230 .borrow_mut()231 .files232 .get_mut(name)233 .unwrap()234 .evaluated235 .replace(value.clone());236 }237 Ok(value)238 })239 }240 pub fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>> {241 Ok(self.settings().import_resolver.resolve_file(from, path)?)242 }243 pub fn load_file_contents(&self, path: &PathBuf) -> Result<Rc<str>> {244 Ok(self.settings().import_resolver.load_file_contents(path)?)245 }246 pub(crate) fn import_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Val> {208 pub(crate) fn import_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Val> {247 let file_path = self.resolve_file(from, path)?;209 let file_path = self.resolve_file(from, path)?;248 {210 {249 let files = &self.data().files;211 let files = &self.data().files;250 if files.contains_key(&file_path) {212 if files.contains_key(&file_path) {251 return self.evaluate_file(&file_path);213 return self.evaluate_loaded_file_raw(&file_path);252 }214 }253 }215 }254 let contents = self.load_file_contents(&file_path)?;216 let contents = self.load_file_contents(&file_path)?;255 self.add_file(file_path.clone(), contents)?;217 self.add_file(file_path.clone(), contents)?;256 self.evaluate_file(&file_path)218 self.evaluate_loaded_file_raw(&file_path)257 }219 }258 pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<str>> {220 pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<str>> {259 let path = self.resolve_file(from, path)?;221 let path = self.resolve_file(from, path)?;264 Ok(self.data().str_files.get(&path).cloned().unwrap())226 Ok(self.data().str_files.get(&path).cloned().unwrap())265 }227 }266228267 /// Parses and evaluates snippet229 fn evaluate_loaded_file_raw(&self, name: &PathBuf) -> Result<Val> {268 pub fn parse_evaluate_raw(&self, source: Rc<PathBuf>, code: &str) -> Result<Val> {269 let parsed = parse(230 let expr: LocExpr = {270 &code,231 let ro_map = &self.data().files;271 &ParserSettings {232 let value = ro_map272 file_name: source,233 .get(name)234 .unwrap_or_else(|| panic!("file not added: {:?}", name));273 loc_data: true,235 if let Some(ref evaluated) = value.evaluated {236 return Ok(evaluated.clone());274 },237 }275 )238 value.parsed.clone()276 .unwrap();239 };240 let value = evaluate(self.create_default_context()?, &expr)?;277 self.evaluate_raw(parsed)241 {242 self.data_mut()243 .files244 .get_mut(name)245 .unwrap()246 .evaluated247 .replace(value.clone());248 }249 Ok(value)278 }250 }279 /// Evaluates parsed expression280 pub fn evaluate_raw(&self, code: LocExpr) -> Result<Val> {281 self.run_in_state(|| evaluate(self.create_default_context()?, &code))282 }283251284 /// Adds standard library global variable (std) to this evaluator252 /// Adds standard library global variable (std) to this evaluator285 pub fn with_stdlib(&self) -> &Self {253 pub fn with_stdlib(&self) -> &Self {292 builtin::get_parsed_stdlib(),260 builtin::get_parsed_stdlib(),293 )261 )294 .unwrap();262 .unwrap();295 let val = self.evaluate_file(&std_path).unwrap();263 let val = self.evaluate_loaded_file_raw(&std_path).unwrap();296 self.settings_mut().globals.insert("std".into(), val);264 self.settings_mut().globals.insert("std".into(), val);297 });265 });298 self266 self321 {289 {322 let mut data = self.data_mut();290 let mut data = self.data_mut();323 let stack_depth = &mut data.stack_depth;291 let stack_depth = &mut data.stack_depth;324 if *stack_depth > self.settings().max_stack {292 if *stack_depth > self.max_stack() {325 // Error creation uses data, so i drop guard here293 // Error creation uses data, so i drop guard here326 drop(data);294 drop(data);327 return Err(self.error(Error::StackOverflow));295 return Err(create_error(Error::StackOverflow));328 } else {296 } else {329 *stack_depth += 1;297 *stack_depth += 1;330 }298 }331 }299 }332 let result = f();300 let result = f();333 self.data_mut().stack_depth -= 1;301 self.data_mut().stack_depth -= 1;334 if let Err(mut err) = result {302 if let Err(mut err) = result {335 (err.1).0.push(StackTraceElement(e.clone(), frame_desc()));303 (err.1).0.push(StackTraceElement {304 location: e.clone(),305 desc: frame_desc(),306 });336 return Err(err);307 return Err(err);337 }308 }338 result309 result339 }310 }340311341 /// Creates error with stack trace342 pub fn error(&self, err: Error) -> LocError {343 LocError(err, StackTrace(vec![]))344 }345346 /// Runs passed function in state (required, if function needs to modify stack trace)312 /// Runs passed function in state (required, if function needs to modify stack trace)347 pub fn run_in_state<T>(&self, f: impl FnOnce() -> T) -> T {313 pub fn run_in_state<T>(&self, f: impl FnOnce() -> T) -> T {348 EVAL_STATE.with(|v| {314 EVAL_STATE.with(|v| {356 }322 }357 result323 result358 })324 })325 }326327 pub fn stringify_err(&self, e: &LocError) -> String {328 let mut out = String::new();329 self.settings()330 .trace_format331 .write_trace(&mut out, self, e)332 .unwrap();333 out334 }335336 pub fn manifest(&self, val: Val) -> Result<Rc<str>> {337 Ok(match self.manifest_format() {338 ManifestFormat::Yaml(padding) => val.into_yaml(padding)?,339 ManifestFormat::Json(padding) => val.into_json(padding)?,340 ManifestFormat::None => match val {341 Val::Str(s) => s,342 _ => return Err(create_error(Error::StringManifestOutputIsNotAString)),343 },344 })345 }346347 /// If passed value is function - call with set TLA348 pub fn with_tla(&self, val: Val) -> Result<Val> {349 Ok(match val {350 Val::Func(func) => func.evaluate_map(351 self.create_default_context()?,352 &self.settings().tla_vars,353 true,354 )?,355 v => v,356 })357 }358}359360/// Internals361impl EvaluationState {362 fn data(&self) -> Ref<EvaluationData> {363 self.0.data.borrow()364 }365 fn data_mut(&self) -> RefMut<EvaluationData> {366 self.0.data.borrow_mut()367 }368 pub fn settings(&self) -> Ref<EvaluationSettings> {369 self.0.settings.borrow()370 }371 pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {372 self.0.settings.borrow_mut()373 }374}375376/// Raw methods evaluates passed values, but not performs TLA execution377impl EvaluationState {378 pub fn evaluate_file_raw(&self, name: &PathBuf) -> Result<Val> {379 self.import_file(&std::env::current_dir().expect("cwd"), &name)380 }381 pub fn evaluate_file_raw_nocwd(&self, name: &PathBuf) -> Result<Val> {382 self.import_file(&PathBuf::from("."), &name)383 }384 /// Parses and evaluates snippet385 pub fn evaluate_snippet_raw(&self, source: Rc<PathBuf>, code: Rc<str>) -> Result<Val> {386 let parsed = parse(387 &code,388 &ParserSettings {389 file_name: source.clone(),390 loc_data: true,391 },392 )393 .unwrap();394 self.add_parsed_file(source, code, parsed.clone())?;395 self.evaluate_expr_raw(parsed)396 }397 /// Evaluates parsed expression398 pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result<Val> {399 evaluate(self.create_default_context()?, &code)400 }401}402403/// Settings utilities404impl EvaluationState {405 pub fn add_ext_var(&self, name: Rc<str>, value: Val) {406 self.settings_mut().ext_vars.insert(name, value);407 }408 pub fn add_ext_str(&self, name: Rc<str>, value: Rc<str>) {409 self.add_ext_var(name, Val::Str(value));410 }411 pub fn add_ext_code(&self, name: Rc<str>, code: Rc<str>) -> Result<()> {412 let value =413 self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("ext_code {}", name))), code)?;414 self.add_ext_var(name, value);415 Ok(())416 }417418 pub fn add_tla(&self, name: Rc<str>, value: Val) {419 self.settings_mut().tla_vars.insert(name, value);420 }421 pub fn add_tla_str(&self, name: Rc<str>, value: Rc<str>) {422 self.add_tla(name, Val::Str(value));423 }424 pub fn add_tla_code(&self, name: Rc<str>, code: Rc<str>) -> Result<()> {425 let value =426 self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("tla_code {}", name))), code)?;427 self.add_ext_var(name, value);428 Ok(())429 }430431 pub fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>> {432 Ok(self.settings().import_resolver.resolve_file(from, path)?)433 }434 pub fn load_file_contents(&self, path: &PathBuf) -> Result<Rc<str>> {435 Ok(self.settings().import_resolver.load_file_contents(path)?)436 }437438 pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {439 Ref::map(self.settings(), |s| &*s.import_resolver)440 }441 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {442 self.settings_mut().import_resolver = resolver;443 }444445 pub fn manifest_format(&self) -> ManifestFormat {446 self.settings().manifest_format.clone()447 }448 pub fn set_manifest_format(&self, format: ManifestFormat) {449 self.settings_mut().manifest_format = format;450 }451452 pub fn trace_format(&self) -> Ref<dyn TraceFormat> {453 Ref::map(self.settings(), |s| &*s.trace_format)454 }455 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {456 self.settings_mut().trace_format = format;457 }458459 pub fn max_trace(&self) -> usize {460 self.settings().max_trace461 }462 pub fn set_max_trace(&self, trace: usize) {463 self.settings_mut().max_trace = trace;464 }465466 pub fn max_stack(&self) -> usize {467 self.settings().max_stack468 }469 pub fn set_max_stack(&self, trace: usize) {470 self.settings_mut().max_stack = trace;359 }471 }360}472}361473393 state.with_stdlib();505 state.with_stdlib();394 assert!(primitive_equals(506 assert!(primitive_equals(395 &state507 &state396 .parse_evaluate_raw(508 .evaluate_snippet_raw(397 Rc::new(PathBuf::from("raw.jsonnet")),509 Rc::new(PathBuf::from("raw.jsonnet")),398 r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#510 r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#.into()399 )511 )400 .unwrap(),512 .unwrap(),401 &Val::Bool(true),513 &Val::Bool(true),407 ($str: expr) => {519 ($str: expr) => {408 EvaluationState::default()520 EvaluationState::default()409 .with_stdlib()521 .with_stdlib()410 .parse_evaluate_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str)522 .evaluate_snippet_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str.into())411 .unwrap()523 .unwrap()412 };524 };413 }525 }417 evaluator.with_stdlib();529 evaluator.with_stdlib();418 evaluator.run_in_state(|| {530 evaluator.run_in_state(|| {419 evaluator531 evaluator420 .parse_evaluate_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str)532 .evaluate_snippet_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str.into())421 .unwrap()533 .unwrap()422 .into_json(0)534 .into_json(0)423 .unwrap()535 .unwrap()