git.delta.rocks / jrsonnet / refs/commits / 9d2a45fdc434

difftreelog

perf cleanup ObjValue

Yaroslav Bolyukin2021-02-20parent: #67899df.patch.diff
in: master

2 files changed

modifiedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/builtin/manifest.rs
1use crate::error::Error::*;2use crate::error::Result;3use crate::{throw, Val};45#[derive(PartialEq, Clone, Copy)]6pub enum ManifestType {7	// Applied in manifestification8	Manifest,9	/// Used for std.manifestJson10	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest11	Std,12	/// No line breaks, used in `obj+''`13	ToString,14	/// Minified json15	Minify,16}1718pub struct ManifestJsonOptions<'s> {19	pub padding: &'s str,20	pub mtype: ManifestType,21}2223pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {24	let mut out = String::new();25	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;26	Ok(out)27}28fn manifest_json_ex_buf(29	val: &Val,30	buf: &mut String,31	cur_padding: &mut String,32	options: &ManifestJsonOptions<'_>,33) -> Result<()> {34	use std::fmt::Write;35	let mtype = options.mtype;36	match val {37		Val::Bool(v) => {38			if *v {39				buf.push_str("true");40			} else {41				buf.push_str("false");42			}43		}44		Val::Null => buf.push_str("null"),45		Val::Str(s) => buf.push_str(&escape_string_json(s)),46		Val::Num(n) => write!(buf, "{}", n).unwrap(),47		Val::Arr(items) => {48			buf.push('[');49			if !items.is_empty() {50				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {51					buf.push('\n');52				}5354				let old_len = cur_padding.len();55				cur_padding.push_str(options.padding);56				for (i, item) in items.iter().enumerate() {57					if i != 0 {58						buf.push(',');59						if mtype == ManifestType::ToString {60							buf.push(' ');61						} else if mtype != ManifestType::Minify {62							buf.push('\n');63						}64					}65					buf.push_str(cur_padding);66					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;67				}68				cur_padding.truncate(old_len);6970				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {71					buf.push('\n');72					buf.push_str(cur_padding);73				}74			} else if mtype == ManifestType::Std {75				buf.push_str("\n\n");76				buf.push_str(cur_padding);77			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {78				buf.push(' ');79			}80			buf.push(']');81		}82		Val::Obj(obj) => {83			buf.push('{');84			let fields = obj.visible_fields();85			if !fields.is_empty() {86				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {87					buf.push('\n');88				}8990				let old_len = cur_padding.len();91				cur_padding.push_str(options.padding);92				for (i, field) in fields.into_iter().enumerate() {93					if i != 0 {94						buf.push(',');95						if mtype == ManifestType::ToString {96							buf.push(' ');97						} else if mtype != ManifestType::Minify {98							buf.push('\n');99						}100					}101					buf.push_str(cur_padding);102					buf.push_str(&escape_string_json(&field));103					buf.push_str(": ");104					manifest_json_ex_buf(&obj.get(field)?.unwrap(), buf, cur_padding, options)?;105				}106				cur_padding.truncate(old_len);107108				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {109					buf.push('\n');110					buf.push_str(cur_padding);111				}112			} else if mtype == ManifestType::Std {113				buf.push_str("\n\n");114				buf.push_str(cur_padding);115			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {116				buf.push(' ');117			}118			buf.push('}');119		}120		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),121	};122	Ok(())123}124pub fn escape_string_json(s: &str) -> String {125	use std::fmt::Write;126	let mut out = String::new();127	out.push('"');128	for c in s.chars() {129		match c {130			'"' => out.push_str("\\\""),131			'\\' => out.push_str("\\\\"),132			'\u{0008}' => out.push_str("\\b"),133			'\u{000c}' => out.push_str("\\f"),134			'\n' => out.push_str("\\n"),135			'\r' => out.push_str("\\r"),136			'\t' => out.push_str("\\t"),137			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {138				write!(out, "\\u{:04x}", c as u32).unwrap()139			}140			c => out.push(c),141		}142	}143	out.push('"');144	out145}146147#[test]148fn json_test() {149	assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")150}
after · crates/jrsonnet-evaluator/src/builtin/manifest.rs
1use crate::error::Error::*;2use crate::error::Result;3use crate::{throw, Val};45#[derive(PartialEq, Clone, Copy)]6pub enum ManifestType {7	// Applied in manifestification8	Manifest,9	/// Used for std.manifestJson10	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest11	Std,12	/// No line breaks, used in `obj+''`13	ToString,14	/// Minified json15	Minify,16}1718pub struct ManifestJsonOptions<'s> {19	pub padding: &'s str,20	pub mtype: ManifestType,21}2223pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {24	let mut out = String::new();25	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;26	Ok(out)27}28fn manifest_json_ex_buf(29	val: &Val,30	buf: &mut String,31	cur_padding: &mut String,32	options: &ManifestJsonOptions<'_>,33) -> Result<()> {34	use std::fmt::Write;35	let mtype = options.mtype;36	match val {37		Val::Bool(v) => {38			if *v {39				buf.push_str("true");40			} else {41				buf.push_str("false");42			}43		}44		Val::Null => buf.push_str("null"),45		Val::Str(s) => buf.push_str(&escape_string_json(s)),46		Val::Num(n) => write!(buf, "{}", n).unwrap(),47		Val::Arr(items) => {48			buf.push('[');49			if !items.is_empty() {50				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {51					buf.push('\n');52				}5354				let old_len = cur_padding.len();55				cur_padding.push_str(options.padding);56				for (i, item) in items.iter().enumerate() {57					if i != 0 {58						buf.push(',');59						if mtype == ManifestType::ToString {60							buf.push(' ');61						} else if mtype != ManifestType::Minify {62							buf.push('\n');63						}64					}65					buf.push_str(cur_padding);66					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;67				}68				cur_padding.truncate(old_len);6970				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {71					buf.push('\n');72					buf.push_str(cur_padding);73				}74			} else if mtype == ManifestType::Std {75				buf.push_str("\n\n");76				buf.push_str(cur_padding);77			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {78				buf.push(' ');79			}80			buf.push(']');81		}82		Val::Obj(obj) => {83			buf.push('{');84			let fields = obj.fields();85			if !fields.is_empty() {86				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {87					buf.push('\n');88				}8990				let old_len = cur_padding.len();91				cur_padding.push_str(options.padding);92				for (i, field) in fields.into_iter().enumerate() {93					if i != 0 {94						buf.push(',');95						if mtype == ManifestType::ToString {96							buf.push(' ');97						} else if mtype != ManifestType::Minify {98							buf.push('\n');99						}100					}101					buf.push_str(cur_padding);102					buf.push_str(&escape_string_json(&field));103					buf.push_str(": ");104					manifest_json_ex_buf(&obj.get(field)?.unwrap(), buf, cur_padding, options)?;105				}106				cur_padding.truncate(old_len);107108				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {109					buf.push('\n');110					buf.push_str(cur_padding);111				}112			} else if mtype == ManifestType::Std {113				buf.push_str("\n\n");114				buf.push_str(cur_padding);115			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {116				buf.push(' ');117			}118			buf.push('}');119		}120		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),121	};122	Ok(())123}124pub fn escape_string_json(s: &str) -> String {125	use std::fmt::Write;126	let mut out = String::new();127	out.push('"');128	for c in s.chars() {129		match c {130			'"' => out.push_str("\\\""),131			'\\' => out.push_str("\\\\"),132			'\u{0008}' => out.push_str("\\b"),133			'\u{000c}' => out.push_str("\\f"),134			'\n' => out.push_str("\\n"),135			'\r' => out.push_str("\\r"),136			'\t' => out.push_str("\\t"),137			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {138				write!(out, "\\u{:04x}", c as u32).unwrap()139			}140			c => out.push(c),141		}142	}143	out.push('"');144	out145}146147#[test]148fn json_test() {149	assert_eq!(escape_string_json("\u{001f}"), "\"\\u001f\"")150}
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -1,8 +1,9 @@
 use crate::{evaluate_add_op, LazyBinding, Result, Val};
-use indexmap::IndexMap;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ExprLocation, Visibility};
-use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
+use rustc_hash::FxHashMap;
+use std::hash::{Hash, Hasher};
+use std::{cell::RefCell, fmt::Debug, rc::Rc};
 
 #[derive(Debug)]
 pub struct ObjMember {
@@ -13,13 +14,15 @@
 }
 
 // Field => This
-type CacheKey = (IStr, usize);
+type CacheKey = (IStr, ObjValue);
 #[derive(Debug)]
 pub struct ObjValueInternals {
 	super_obj: Option<ObjValue>,
-	this_entries: Rc<HashMap<IStr, ObjMember>>,
-	value_cache: RefCell<HashMap<CacheKey, Option<Val>>>,
+	this_obj: Option<ObjValue>,
+	this_entries: Rc<FxHashMap<IStr, ObjMember>>,
+	value_cache: RefCell<FxHashMap<CacheKey, Option<Val>>>,
 }
+
 #[derive(Clone)]
 pub struct ObjValue(pub(crate) Rc<ObjValueInternals>);
 impl Debug for ObjValue {
@@ -48,15 +51,16 @@
 }
 
 impl ObjValue {
-	pub fn new(super_obj: Option<Self>, this_entries: Rc<HashMap<IStr, ObjMember>>) -> Self {
+	pub fn new(super_obj: Option<Self>, this_entries: Rc<FxHashMap<IStr, ObjMember>>) -> Self {
 		Self(Rc::new(ObjValueInternals {
 			super_obj,
+			this_obj: None,
 			this_entries,
-			value_cache: RefCell::new(HashMap::new()),
+			value_cache: RefCell::new(FxHashMap::default()),
 		}))
 	}
 	pub fn new_empty() -> Self {
-		Self::new(None, Rc::new(HashMap::new()))
+		Self::new(None, Rc::new(FxHashMap::default()))
 	}
 	pub fn with_super(&self, super_obj: Self) -> Self {
 		match &self.0.super_obj {
@@ -64,23 +68,37 @@
 			Some(v) => Self::new(Some(v.with_super(super_obj)), self.0.this_entries.clone()),
 		}
 	}
-	pub fn enum_fields(&self, handler: &impl Fn(&IStr, &Visibility)) {
+	pub fn with_this(&self, this_obj: Self) -> Self {
+		Self(Rc::new(ObjValueInternals {
+			super_obj: self.0.super_obj.clone(),
+			this_obj: Some(this_obj),
+			this_entries: self.0.this_entries.clone(),
+			value_cache: RefCell::new(FxHashMap::default()),
+		}))
+	}
+
+	/// Run callback for every field found in object
+	pub(crate) fn enum_fields(&self, handler: &mut impl FnMut(&IStr, &Visibility) -> bool) -> bool {
 		if let Some(s) = &self.0.super_obj {
-			s.enum_fields(handler);
+			if s.enum_fields(handler) {
+				return true;
+			}
 		}
 		for (name, member) in self.0.this_entries.iter() {
-			handler(name, &member.visibility);
+			if handler(name, &member.visibility) {
+				return true;
+			}
 		}
+		false
 	}
-	pub fn fields_visibility(&self) -> IndexMap<IStr, bool> {
-		let out = Rc::new(RefCell::new(IndexMap::new()));
-		self.enum_fields(&|name, visibility| {
-			let mut out = out.borrow_mut();
+
+	pub fn fields_visibility(&self) -> FxHashMap<IStr, bool> {
+		let mut out = FxHashMap::default();
+		self.enum_fields(&mut |name, visibility| {
 			match visibility {
 				Visibility::Normal => {
-					if !out.contains_key(name) {
-						out.insert(name.to_owned(), true);
-					}
+					let entry = out.entry(name.to_owned());
+					entry.or_insert(true);
 				}
 				Visibility::Hidden => {
 					out.insert(name.to_owned(), false);
@@ -89,25 +107,71 @@
 					out.insert(name.to_owned(), true);
 				}
 			};
+			false
 		});
-		Rc::try_unwrap(out).unwrap().into_inner()
+		out
 	}
-	pub fn visible_fields(&self) -> Vec<IStr> {
-		let mut visible_fields: Vec<_> = self
+	pub fn fields_ex(&self, include_hidden: bool) -> Vec<IStr> {
+		let mut fields: Vec<_> = self
 			.fields_visibility()
 			.into_iter()
-			.filter(|(_k, v)| *v)
+			.filter(|(_k, v)| include_hidden || *v)
 			.map(|(k, _)| k)
 			.collect();
-		visible_fields.sort();
-		visible_fields
+		fields.sort_unstable();
+		fields
+	}
+	pub fn fields(&self) -> Vec<IStr> {
+		self.fields_ex(false)
+	}
+
+	pub fn field_visibility(&self, name: IStr) -> Option<Visibility> {
+		if let Some(m) = self.0.this_entries.get(&name) {
+			Some(match &m.visibility {
+				Visibility::Normal => self
+					.0
+					.super_obj
+					.as_ref()
+					.and_then(|super_obj| super_obj.field_visibility(name))
+					.unwrap_or(Visibility::Normal),
+				v => *v,
+			})
+		} else if let Some(super_obj) = &self.0.super_obj {
+			super_obj.field_visibility(name)
+		} else {
+			None
+		}
+	}
+
+	fn has_field_include_hidden(&self, name: IStr) -> bool {
+		if self.0.this_entries.contains_key(&name) {
+			true
+		} else if let Some(super_obj) = &self.0.super_obj {
+			super_obj.has_field_include_hidden(name)
+		} else {
+			false
+		}
+	}
+
+	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {
+		if include_hidden {
+			self.has_field_include_hidden(name)
+		} else {
+			self.has_field(name)
+		}
+	}
+	pub fn has_field(&self, name: IStr) -> bool {
+		self.field_visibility(name)
+			.map(|v| v.is_visible())
+			.unwrap_or(false)
 	}
+
 	pub fn get(&self, key: IStr) -> Result<Option<Val>> {
-		Ok(self.get_raw(key, None)?)
+		self.get_raw(key, self.0.this_obj.as_ref())
 	}
 	pub(crate) fn get_raw(&self, key: IStr, real_this: Option<&Self>) -> Result<Option<Val>> {
 		let real_this = real_this.unwrap_or(self);
-		let cache_key = (key.clone(), Rc::as_ptr(&real_this.0) as usize);
+		let cache_key = (key.clone(), real_this.clone());
 
 		if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {
 			return Ok(v.clone());
@@ -135,17 +199,25 @@
 		Ok(value)
 	}
 	fn evaluate_this(&self, v: &ObjMember, real_this: &Self) -> Result<Val> {
-		Ok(v.invoke
+		v.invoke
 			.evaluate(Some(real_this.clone()), self.0.super_obj.clone())?
-			.evaluate()?)
+			.evaluate()
 	}
 
 	pub fn ptr_eq(a: &Self, b: &Self) -> bool {
 		Rc::ptr_eq(&a.0, &b.0)
 	}
 }
+
 impl PartialEq for ObjValue {
 	fn eq(&self, other: &Self) -> bool {
 		Rc::ptr_eq(&self.0, &other.0)
 	}
 }
+
+impl Eq for ObjValue {}
+impl Hash for ObjValue {
+	fn hash<H: Hasher>(&self, state: &mut H) {
+		state.write_usize(Rc::as_ptr(&self.0) as usize)
+	}
+}