git.delta.rocks / jrsonnet / refs/commits / a0455123d694

difftreelog

perf lesser and cheaper clones

Лач2020-06-26parent: #c1a6c6a.patch.diff
in: master

11 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -5,7 +5,7 @@
 use jsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};
 use location::{offset_to_location, CodeLocation};
 use std::env::current_dir;
-use std::{collections::HashMap, path::PathBuf, str::FromStr};
+use std::{collections::HashMap, path::PathBuf, str::FromStr, rc::Rc};
 
 enum Format {
 	None,
@@ -128,10 +128,10 @@
 		evaluator.with_stdlib();
 	}
 	for ExtStr { name, value } in opts.ext_str.iter().cloned() {
-		evaluator.add_ext_var(name, Val::Str(value));
+		evaluator.add_ext_var(name.into(), Val::Str(value.into()));
 	}
 	for ExtStr { name, value } in opts.ext_code.iter().cloned() {
-		evaluator.add_ext_var(name, evaluator.parse_evaluate_raw(&value).unwrap());
+		evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());
 	}
 	let mut input = current_dir().unwrap();
 	input.push(opts.input.clone());
@@ -147,7 +147,7 @@
 				Val::Func(f) => {
 					let mut desc_map = HashMap::new();
 					for ExtStr { name, value } in opts.tla_str.iter().cloned() {
-						desc_map.insert(name, el!(Expr::Str(value)));
+						desc_map.insert(name, el!(Expr::Str(value.into())));
 					}
 					for ExtStr { name, value } in opts.tla_code.iter().cloned() {
 						desc_map.insert(
@@ -155,17 +155,17 @@
 							jsonnet_parser::parse(
 								&value,
 								&ParserSettings {
-									file_name: PathBuf::new(),
+									file_name: Rc::new(PathBuf::new()),
 									loc_data: false,
 								},
 							)
 							.unwrap(),
 						);
 					}
-					evaluator.add_global("__tmp__tlf__".to_owned(), Val::Func(f));
+					evaluator.add_global("__tmp__tlf__".into(), Val::Func(f));
 					evaluator
 						.evaluate_raw(el!(Expr::Apply(
-							el!(Expr::Var("__tmp__tlf__".to_owned())),
+							el!(Expr::Var("__tmp__tlf__".into())),
 							ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),
 							false,
 						)))
@@ -178,7 +178,7 @@
 					if opts.no_stdlib {
 						evaluator.with_stdlib();
 					}
-					evaluator.add_global("__tmp__to_json__".to_owned(), v);
+					evaluator.add_global("__tmp__to_json__".into(), v);
 					let v = evaluator.parse_evaluate_raw(&format!(
 						"std.manifestJsonEx(__tmp__to_json__, \"{}\")",
 						" ".repeat(opts.line_padding),
@@ -195,7 +195,7 @@
 					if opts.no_stdlib {
 						evaluator.with_stdlib();
 					}
-					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);
+					evaluator.add_global("__tmp__to_yaml__".into(), v);
 					let v = evaluator
 						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");
 					match v {
modifiedcrates/jsonnet-evaluator/build.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/build.rs
+++ b/crates/jsonnet-evaluator/build.rs
@@ -34,7 +34,7 @@
 								Member::Field(FieldMember {
 									name: FieldName::Fixed(name),
 									..
-								}) if name == "join"
+								}) if **name == *"join"
 							)
 						})
 						.collect(),
modifiedcrates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/ctx.rs
+++ b/crates/jsonnet-evaluator/src/ctx.rs
@@ -16,7 +16,7 @@
 	dollar: Option<ObjValue>,
 	this: Option<ObjValue>,
 	super_obj: Option<ObjValue>,
-	bindings: LayeredHashMap<String, LazyVal>,
+	bindings: LayeredHashMap<Rc<str>, LazyVal>,
 }
 pub struct Context(Rc<ContextInternals>);
 impl Debug for Context {
@@ -53,9 +53,9 @@
 		}))
 	}
 
-	pub fn binding(&self, name: &str) -> Result<LazyVal> {
-		self.0.bindings.get(name).cloned().ok_or_else(|| {
-			create_error::<()>(Error::UnknownVariable(name.to_owned()))
+	pub fn binding(&self, name: Rc<str>) -> Result<LazyVal> {
+		self.0.bindings.get(&name).cloned().ok_or_else(|| {
+			create_error::<()>(Error::UnknownVariable(name))
 				.err()
 				.unwrap()
 		})
@@ -67,7 +67,7 @@
 		ctx.unwrap()
 	}
 
-	pub fn with_var(&self, name: String, value: Val) -> Result<Context> {
+	pub fn with_var(&self, name: Rc<str>, value: Val) -> Result<Context> {
 		let mut new_bindings = HashMap::with_capacity(1);
 		new_bindings.insert(name, resolved_lazy_val!(value));
 		self.extend(new_bindings, None, None, None)
@@ -75,7 +75,7 @@
 
 	pub fn extend(
 		&self,
-		new_bindings: HashMap<String, LazyVal>,
+		new_bindings: HashMap<Rc<str>, LazyVal>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
@@ -97,7 +97,7 @@
 	}
 	pub fn extend_unbound(
 		&self,
-		new_bindings: HashMap<String, LazyBinding>,
+		new_bindings: HashMap<Rc<str>, LazyBinding>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
modifiedcrates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/error.rs
+++ b/crates/jsonnet-evaluator/src/error.rs
@@ -6,20 +6,20 @@
 pub enum Error {
 	VariableIsNotDefined(String),
 	TypeMismatch(&'static str, Vec<ValType>, ValType),
-	NoSuchField(String),
+	NoSuchField(Rc<str>),
 
-	UnknownVariable(String),
+	UnknownVariable(Rc<str>),
 
 	UnknownFunctionParameter(String),
-	BindingParameterASecondTime(String),
+	BindingParameterASecondTime(Rc<str>),
 	TooManyArgsFunctionHas(usize),
-	FunctionParameterNotBoundInCall(String),
+	FunctionParameterNotBoundInCall(Rc<str>),
 
-	UndefinedExternalVariable(String),
+	UndefinedExternalVariable(Rc<str>),
 
 	FieldMustBeStringGot(ValType),
 
-	AttemptedIndexAnArrayWithString(String),
+	AttemptedIndexAnArrayWithString(Rc<str>),
 	ValueIndexMustBeTypeGot(ValType, ValType, ValType),
 	CantIndexInto(ValType),
 
@@ -31,7 +31,7 @@
 	ImportNotSupported(PathBuf, PathBuf),
 	ImportSyntaxError(jsonnet_parser::ParseError),
 
-	RuntimeError(String),
+	RuntimeError(Rc<str>),
 	StackOverflow,
 	FractionalIndex,
 	DivisionByZero,
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -14,7 +14,7 @@
 	rc::Rc,
 };
 
-pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (String, LazyBinding) {
+pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc<str>, LazyBinding) {
 	let b = b.clone();
 	if let Some(params) = &b.params {
 		let params = params.clone();
@@ -54,11 +54,12 @@
 pub fn evaluate_field_name(
 	context: Context,
 	field_name: &jsonnet_parser::FieldName,
-) -> Result<Option<String>> {
+) -> Result<Option<Rc<str>>> {
 	Ok(match field_name {
 		jsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),
 		jsonnet_parser::FieldName::Dyn(expr) => {
-			let value = evaluate(context, expr)?.unwrap_if_lazy()?;
+			let lazy = evaluate(context, expr)?;
+			let value = lazy.unwrap_if_lazy()?;
 			if matches!(value, Val::Null) {
 				None
 			} else {
@@ -80,17 +81,17 @@
 
 pub(crate) fn evaluate_add_op(a: &Val, b: &Val) -> Result<Val> {
 	Ok(match (a, b) {
-		(Val::Str(v1), Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
+		(Val::Str(v1), Val::Str(v2)) => Val::Str(((**v1).to_owned() + &v2).into()),
 
 		// Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890)
-		(Val::Num(n), Val::Str(o)) => Val::Str(format!("{}{}", n, o)),
-		(Val::Str(o), Val::Num(n)) => Val::Str(format!("{}{}", o, n)),
+		(Val::Num(n), Val::Str(o)) => Val::Str(format!("{}{}", n, o).into()),
+		(Val::Str(o), Val::Num(n)) => Val::Str(format!("{}{}", o, n).into()),
 
-		(Val::Str(s), o) => Val::Str(format!("{}{}", s, o.clone().into_json(0)?)),
-		(o, Val::Str(s)) => Val::Str(format!("{}{}", o.clone().into_json(0)?, s)),
+		(Val::Str(s), o) => Val::Str(format!("{}{}", s, o.clone().into_json(0)?).into()),
+		(o, Val::Str(s)) => Val::Str(format!("{}{}", o.clone().into_json(0)?, s).into()),
 
 		(Val::Obj(v1), Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
-		(Val::Arr(a), Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),
+		(Val::Arr(a), Val::Arr(b)) => Val::Arr(Rc::new([&a[..], &b[..]].concat())),
 		(Val::Num(v1), Val::Num(v2)) => Val::Num(v1 + v2),
 		_ => panic!("can't add: {:?} and {:?}", a, b),
 	})
@@ -117,7 +118,7 @@
 	Ok(match (a, op, b) {
 		(a, BinaryOpType::Add, b) => evaluate_add_op(a, b)?,
 
-		(Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize)),
+		(Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize).into()),
 
 		// Bool X Bool
 		(Val::Bool(a), BinaryOpType::And, Val::Bool(b)) => Val::Bool(*a && *b),
@@ -165,7 +166,7 @@
 	})
 }
 
-future_wrapper!(HashMap<String, LazyBinding>, FutureNewBindings);
+future_wrapper!(HashMap<Rc<str>, LazyBinding>, FutureNewBindings);
 future_wrapper!(ObjValue, FutureObjValue);
 
 pub fn evaluate_comp<T>(
@@ -186,10 +187,10 @@
 			match evaluate(context.clone(), &expr)?.unwrap_if_lazy()? {
 				Val::Arr(list) => {
 					let mut out = Vec::new();
-					for item in list {
-						let item = item.clone().unwrap_if_lazy()?;
+					for item in list.iter() {
+						let item = item.unwrap_if_lazy()?;
 						out.push(evaluate_comp(
-							context.with_var(var.clone(), item)?,
+							context.with_var(var.clone(), item.clone())?,
 							value,
 							&specs[1..],
 						)?);
@@ -202,113 +203,108 @@
 	})
 }
 
-// TODO: Asserts
-pub fn evaluate_object(context: Context, object: ObjBody) -> Result<ObjValue> {
-	Ok(match object {
-		ObjBody::MemberList(members) => {
-			let new_bindings = FutureNewBindings::new();
-			let future_this = FutureObjValue::new();
-			let context_creator = context_creator!(
-				closure!(clone context, clone new_bindings, |this: Option<ObjValue>, super_obj: Option<ObjValue>| {
-					Ok(context.extend_unbound(
-						new_bindings.clone().unwrap(),
-						context.dollar().clone().or_else(||this.clone()),
-						Some(this.unwrap()),
-						super_obj
-					)?)
-				})
-			);
-			{
-				let mut bindings: HashMap<String, LazyBinding> = HashMap::new();
-				for (n, b) in members
-					.iter()
-					.filter_map(|m| match m {
-						Member::BindStmt(b) => Some(b.clone()),
-						_ => None,
-					})
-					.map(|b| evaluate_binding(&b, context_creator.clone()))
-				{
-					bindings.insert(n, b);
+pub fn evaluate_member_list_object(context: Context, members: &Vec<Member>) -> Result<ObjValue> {
+	let new_bindings = FutureNewBindings::new();
+	let future_this = FutureObjValue::new();
+	let context_creator = context_creator!(
+		closure!(clone context, clone new_bindings, |this: Option<ObjValue>, super_obj: Option<ObjValue>| {
+			Ok(context.extend_unbound(
+				new_bindings.clone().unwrap(),
+				context.dollar().clone().or_else(||this.clone()),
+				Some(this.unwrap()),
+				super_obj
+			)?)
+		})
+	);
+	{
+		let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
+		for (n, b) in members
+			.iter()
+			.filter_map(|m| match m {
+				Member::BindStmt(b) => Some(b.clone()),
+				_ => None,
+			})
+			.map(|b| evaluate_binding(&b, context_creator.clone()))
+		{
+			bindings.insert(n, b);
+		}
+		new_bindings.fill(bindings);
+	}
+
+	let mut new_members = BTreeMap::new();
+	for member in members.iter() {
+		match member {
+			Member::Field(FieldMember {
+				name,
+				plus,
+				params: None,
+				visibility,
+				value,
+			}) => {
+				let name = evaluate_field_name(context.clone(), &name)?;
+				if name.is_none() {
+					continue;
 				}
-				new_bindings.fill(bindings);
+				let name = name.unwrap();
+				new_members.insert(
+					name.clone(),
+					ObjMember {
+						add: *plus,
+						visibility: *visibility,
+						invoke: LazyBinding::Bindable(Rc::new(
+							closure!(clone name, clone value, clone context_creator, |this, super_obj| {
+								Ok(LazyVal::new_resolved(push(&value.1, "object field", ||{
+									let context = context_creator.0(this, super_obj)?;
+									evaluate(
+										context,
+										&value,
+									)
+								})?))
+							}),
+						)),
+					},
+				);
 			}
-
-			let mut new_members = BTreeMap::new();
-			for member in members.into_iter() {
-				match member {
-					Member::Field(FieldMember {
-						name,
-						plus,
-						params: None,
-						visibility,
-						value,
-					}) => {
-						let name = evaluate_field_name(context.clone(), &name)?;
-						if name.is_none() {
-							continue;
-						}
-						let name = name.unwrap();
-						new_members.insert(
-							name.clone(),
-							ObjMember {
-								add: plus,
-								visibility: visibility.clone(),
-								invoke: LazyBinding::Bindable(Rc::new(
-									closure!(clone name, clone value, clone context_creator, |this, super_obj| {
-										Ok(LazyVal::new_resolved(push(&value.1, "object field", ||{
-											let context = context_creator.0(this, super_obj)?;
-											evaluate(
-												context,
-												&value,
-											)?.unwrap_if_lazy()
-										})?))
-									}),
-								)),
-							},
-						);
-					}
-					Member::Field(FieldMember {
-						name,
-						params: Some(params),
-						value,
-						..
-					}) => {
-						let name = evaluate_field_name(context.clone(), &name)?;
-						if name.is_none() {
-							continue;
-						}
-						let name = name.unwrap();
-						new_members.insert(
-							name,
-							ObjMember {
-								add: false,
-								visibility: Visibility::Hidden,
-								invoke: LazyBinding::Bindable(Rc::new(
-									closure!(clone value, clone context_creator, |this, super_obj| {
-										// TODO: Assert
-										Ok(LazyVal::new_resolved(evaluate_method(
-											context_creator.0(this, super_obj)?,
-											params.clone(),
-											value.clone(),
-										)))
-									}),
-								)),
-							},
-						);
-					}
-					Member::BindStmt(_) => {}
-					Member::AssertStmt(_) => {}
+			Member::Field(FieldMember {
+				name,
+				params: Some(params),
+				value,
+				..
+			}) => {
+				let name = evaluate_field_name(context.clone(), &name)?;
+				if name.is_none() {
+					continue;
 				}
+				let name = name.unwrap();
+				new_members.insert(
+					name,
+					ObjMember {
+						add: false,
+						visibility: Visibility::Hidden,
+						invoke: LazyBinding::Bindable(Rc::new(
+							closure!(clone value, clone context_creator, clone params, |this, super_obj| {
+								// TODO: Assert
+								Ok(LazyVal::new_resolved(evaluate_method(
+									context_creator.0(this, super_obj)?,
+									params.clone(),
+									value.clone(),
+								)))
+							}),
+						)),
+					},
+				);
 			}
-			future_this.fill(ObjValue::new(None, Rc::new(new_members)))
+			Member::BindStmt(_) => {}
+			Member::AssertStmt(_) => {}
 		}
-		ObjBody::ObjComp {
-			pre_locals,
-			key,
-			value,
-			post_locals,
-			compspecs,
-		} => {
+	}
+	Ok(future_this.fill(ObjValue::new(None, Rc::new(new_members))))
+}
+
+pub fn evaluate_object(context: Context, object: &ObjBody) -> Result<ObjValue> {
+	Ok(match object {
+		ObjBody::MemberList(members) => evaluate_member_list_object(context, &members)?,
+		ObjBody::ObjComp(obj) => {
 			let future_this = FutureObjValue::new();
 			let mut new_members = BTreeMap::new();
 			for (k, v) in evaluate_comp(
@@ -325,26 +321,27 @@
 							)?)
 						})
 					);
-					let mut bindings: HashMap<String, LazyBinding> = HashMap::new();
-					for (n, b) in pre_locals
+					let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
+					for (n, b) in obj
+						.pre_locals
 						.iter()
-						.chain(post_locals.iter())
+						.chain(obj.post_locals.iter())
 						.map(|b| evaluate_binding(b, context_creator.clone()))
 					{
 						bindings.insert(n, b);
 					}
 					let bindings = new_bindings.fill(bindings);
 					let ctx = ctx.extend_unbound(bindings, None, None, None)?;
-					let key = evaluate(ctx.clone(), &key)?;
+					let key = evaluate(ctx.clone(), &obj.key)?;
 					let value = LazyBinding::Bindable(Rc::new(
-						closure!(clone ctx, clone value, |this, _super_obj| {
+						closure!(clone ctx, clone obj.value, |this, _super_obj| {
 							Ok(LazyVal::new_resolved(evaluate(ctx.extend(HashMap::new(), None, this, None)?, &value)?))
 						}),
 					));
 
 					Ok((key, value))
 				},
-				&compspecs,
+				&obj.compspecs,
 			)?
 			.unwrap()
 			{
@@ -394,7 +391,7 @@
 		BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, &v1, *o, &v2)?,
 		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,
 		Var(name) => push(loc, "var", || {
-			Val::Lazy(context.binding(&name)?).unwrap_if_lazy()
+			Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?)
 		})?,
 		Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => {
 			let name = evaluate(context.clone(), index)?.try_cast_str("object index")?;
@@ -411,9 +408,9 @@
 				evaluate(context, index)?,
 			) {
 				(Val::Obj(v), Val::Str(s)) => {
-					if let Some(v) = v.get(&s)? {
+					if let Some(v) = v.get(s.clone())? {
 						v.unwrap_if_lazy()?
-					} else if let Some(Val::Str(n)) = v.get("__intristic_namespace__")? {
+					} else if let Some(Val::Str(n)) = v.get("__intristic_namespace__".into())? {
 						Val::Intristic(n, s)
 					} else {
 						create_error(crate::Error::NoSuchField(s))?
@@ -443,9 +440,13 @@
 					n.value_type()?,
 				))?,
 
-				(Val::Str(s), Val::Num(n)) => {
-					Val::Str(s.chars().skip(n as usize).take(1).collect())
-				}
+				(Val::Str(s), Val::Num(n)) => Val::Str(
+					s.chars()
+						.skip(n as usize)
+						.take(1)
+						.collect::<String>()
+						.into(),
+				),
 				(Val::Str(_), n) => create_error(crate::Error::ValueIndexMustBeTypeGot(
 					ValType::Str,
 					ValType::Num,
@@ -456,7 +457,7 @@
 			}
 		}
 		LocalExpr(bindings, returned) => {
-			let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();
+			let mut new_bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
 			let future_context = Context::new_future();
 
 			let context_creator = context_creator!(
@@ -484,19 +485,20 @@
 					})
 				)));
 			}
-			Val::Arr(out)
+			Val::Arr(Rc::new(out))
 		}
 		ArrComp(expr, compspecs) => Val::Arr(
 			// First compspec should be forspec, so no "None" possible here
-			evaluate_comp(context, &|ctx| evaluate(ctx, expr), compspecs)?.unwrap(),
+			Rc::new(evaluate_comp(context, &|ctx| evaluate(ctx, expr), compspecs)?.unwrap()),
 		),
-		Obj(body) => Val::Obj(evaluate_object(context, body.clone())?),
+		Obj(body) => Val::Obj(evaluate_object(context, body)?),
 		ObjExtend(s, t) => evaluate_add_op(
 			&evaluate(context.clone(), s)?,
-			&Val::Obj(evaluate_object(context, t.clone())?),
+			&Val::Obj(evaluate_object(context, t)?),
 		)?,
 		Apply(value, args, tailstrict) => {
-			let value = evaluate(context.clone(), value)?.unwrap_if_lazy()?;
+			let lazy = evaluate(context.clone(), value)?;
+			let value = lazy.unwrap_if_lazy()?;
 			match value {
 				Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
 					// arr/string/function
@@ -519,7 +521,7 @@
 					("std", "type") => {
 						assert_eq!(args.len(), 1);
 						let expr = &args.get(0).unwrap().1;
-						Val::Str(evaluate(context, expr)?.value_type()?.name().to_owned())
+						Val::Str(evaluate(context, expr)?.value_type()?.name().into())
 					}
 					// length, idx=>any
 					("std", "makeArray") => {
@@ -532,14 +534,14 @@
 							let mut out = Vec::with_capacity(v as usize);
 							for i in 0..v as usize {
 								let call_ctx =
-									Context::new().with_var("v".to_owned(), Val::Num(i as f64))?;
+									Context::new().with_var("v".into(), Val::Num(i as f64))?;
 								out.push(d.evaluate(
 									call_ctx,
-									&ArgsDesc(vec![Arg(None, el!(Expr::Var("v".to_owned())))]),
+									&ArgsDesc(vec![Arg(None, el!(Expr::Var("v".into())))]),
 									true,
 								)?)
 							}
-							Val::Arr(out)
+							Val::Arr(Rc::new(out))
 						} else {
 							panic!("bad makeArray call");
 						}
@@ -564,13 +566,13 @@
 							evaluate(context.clone(), &args[0].1)?,
 							evaluate(context, &args[1].1)?,
 						) {
-							Val::Arr(
+							Val::Arr(Rc::new(
 								body.fields_visibility()
 									.into_iter()
 									.filter(|(_k, v)| *v || include_hidden)
 									.map(|(k, _v)| Val::Str(k))
 									.collect(),
-							)
+							))
 						} else {
 							panic!("bad objectFieldsEx call");
 						}
@@ -587,7 +589,7 @@
 								body.fields_visibility()
 									.into_iter()
 									.filter(|(_k, v)| *v || include_hidden)
-									.any(|(k, _v)| k == name),
+									.any(|(k, _v)| *k == *name),
 							)
 						} else {
 							panic!("bad objectHasEx call");
@@ -664,8 +666,8 @@
 							evaluate(context.clone(), &args[0].1)?,
 							evaluate(context.clone(), &args[1].1)?,
 						) {
-							Val::Arr(
-								arr.into_iter()
+							Val::Arr(Rc::new(
+								arr.iter()
 									.filter(|e| {
 										predicate
 											.evaluate_values(context.clone(), &[e.clone()])
@@ -673,8 +675,9 @@
 											.try_cast_bool("filter predicate")
 											.unwrap()
 									})
+									.cloned()
 									.collect(),
-							)
+							))
 						} else {
 							panic!("bad filter call");
 						}
@@ -682,33 +685,35 @@
 					// faster
 					("std", "join") => {
 						assert_eq!(args.len(), 2);
-						let joiner = evaluate(context.clone(), &args[0].1)?.unwrap_if_lazy()?;
-						let items = evaluate(context, &args[1].1)?.unwrap_if_lazy()?;
-						match (joiner, items) {
+						let joiner = evaluate(context.clone(), &args[0].1)?;
+						let items = evaluate(context, &args[1].1)?;
+						match (joiner.unwrap_if_lazy()?, items.unwrap_if_lazy()?) {
 							(Val::Arr(joiner_items), Val::Arr(items)) => {
 								// TODO: Minimal size should be known
 								let mut out = Vec::new();
 
 								let mut first = true;
-								for item in items {
+								for item in items.iter().cloned() {
 									if let Val::Arr(items) = item.unwrap_if_lazy()? {
 										if !first {
+											out.reserve(joiner_items.len());
 											out.extend(joiner_items.iter().cloned());
 										}
 										first = false;
-										out.extend(items);
+										out.reserve(items.len());
+										out.extend(items.iter().cloned());
 									} else {
 										panic!("all array items should be arrays")
 									}
 								}
 
-								Val::Arr(out)
+								Val::Arr(Rc::new(out))
 							}
 							(Val::Str(joiner), Val::Arr(items)) => {
 								let mut out = String::new();
 
 								let mut first = true;
-								for item in items {
+								for item in items.iter().cloned() {
 									if let Val::Str(item) = item.unwrap_if_lazy()? {
 										if !first {
 											out += &joiner;
@@ -720,7 +725,7 @@
 									}
 								}
 
-								Val::Str(out)
+								Val::Str(out.into())
 							}
 							(joiner, items) => panic!("bad join call: {:?} {:?}", joiner, items),
 						}
@@ -776,20 +781,20 @@
 			}
 		}
 		Import(path) => {
-			let mut import_location = loc
+			let mut tmp = loc
 				.clone()
 				.expect("imports can't be used without loc_data")
-				.0
-				.clone();
+				.0;
+			let import_location = Rc::make_mut(&mut tmp);
 			import_location.pop();
 			with_state(|s| s.import_file(&import_location, path))?
 		}
 		ImportStr(path) => {
-			let mut import_location = loc
+			let mut tmp = loc
 				.clone()
 				.expect("imports can't be used without loc_data")
-				.0
-				.clone();
+				.0;
+			let import_location = Rc::make_mut(&mut tmp);
 			import_location.pop();
 			Val::Str(with_state(|s| s.import_file_str(&import_location, path))?)
 		}
modifiedcrates/jsonnet-evaluator/src/function.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/function.rs
+++ b/crates/jsonnet-evaluator/src/function.rs
@@ -35,7 +35,7 @@
 	let mut positioned_args = vec![None; params.0.len()];
 	for (id, arg) in args.iter().enumerate() {
 		let idx = if let Some(name) = &arg.0 {
-			params.iter().position(|p| &p.0 == name).ok_or_else(|| {
+			params.iter().position(|p| *p.0 == *name).ok_or_else(|| {
 				create_error::<()>(Error::UnknownFunctionParameter(name.clone()))
 					.err()
 					.unwrap()
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -65,11 +65,11 @@
 	/// Contains file source codes and evaluated results for imports and pretty
 	/// printing stacktraces
 	files: RefCell<HashMap<PathBuf, FileData>>,
-	str_files: RefCell<HashMap<PathBuf, String>>,
-	globals: RefCell<HashMap<String, Val>>,
+	str_files: RefCell<HashMap<PathBuf, Rc<str>>>,
+	globals: RefCell<HashMap<Rc<str>, Val>>,
 
 	/// Values to use with std.extVar
-	ext_vars: RefCell<HashMap<String, Val>>,
+	ext_vars: RefCell<HashMap<Rc<str>, Val>>,
 
 	settings: EvaluationSettings,
 	import_resolver: Box<dyn ImportResolver>,
@@ -87,7 +87,7 @@
 	with_state(|s| s.error(err))
 }
 pub(crate) fn push<T>(
-	e: &Option<Rc<ExprLocation>>,
+	e: &Option<ExprLocation>,
 	comment: &str,
 	f: impl FnOnce() -> Result<T>,
 ) -> Result<T> {
@@ -185,11 +185,14 @@
 		})?;
 		self.evaluate_file(&file_path)
 	}
-	pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<String> {
+	pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<str>> {
 		let path = self.0.import_resolver.resolve_file(from, path)?;
 		if !self.0.str_files.borrow().contains_key(&path) {
 			let file_str = self.0.import_resolver.load_file_contents(&path)?;
-			self.0.str_files.borrow_mut().insert(path.clone(), file_str);
+			self.0
+				.str_files
+				.borrow_mut()
+				.insert(path.clone(), file_str.into());
 		}
 		Ok(self.0.str_files.borrow().get(&path).cloned().unwrap())
 	}
@@ -210,10 +213,10 @@
 		self.run_in_state(|| evaluate(self.create_default_context()?, &code))
 	}
 
-	pub fn add_global(&self, name: String, value: Val) {
+	pub fn add_global(&self, name: Rc<str>, value: Val) {
 		self.0.globals.borrow_mut().insert(name, value);
 	}
-	pub fn add_ext_var(&self, name: String, value: Val) {
+	pub fn add_ext_var(&self, name: Rc<str>, value: Val) {
 		self.0.ext_vars.borrow_mut().insert(name, value);
 	}
 
@@ -236,14 +239,14 @@
 					.unwrap();
 			}
 			let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap();
-			self.add_global("std".to_owned(), val);
+			self.add_global("std".into(), val);
 		});
 		self
 	}
 
 	pub fn create_default_context(&self) -> Result<Context> {
 		let globals = self.0.globals.borrow();
-		let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();
+		let mut new_bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
 		for (name, value) in globals.iter() {
 			new_bindings.insert(
 				name.clone(),
@@ -255,7 +258,7 @@
 
 	pub fn push<T>(
 		&self,
-		e: Rc<ExprLocation>,
+		e: ExprLocation,
 		comment: String,
 		f: impl FnOnce() -> Result<T>,
 	) -> Result<T> {
@@ -362,7 +365,7 @@
 			let evaluator = EvaluationState::default();
 			evaluator.with_stdlib();
 			let val = evaluator.parse_evaluate_raw($str).unwrap();
-			evaluator.add_global("__tmp__to_yaml__".to_owned(), val);
+			evaluator.add_global("__tmp__to_yaml__".into(), val);
 			evaluator
 				.parse_evaluate_raw("std.manifestJsonEx(__tmp__to_yaml__, \"\")")
 				.unwrap()
modifiedcrates/jsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/obj.rs
+++ b/crates/jsonnet-evaluator/src/obj.rs
@@ -18,9 +18,10 @@
 #[derive(Debug)]
 pub struct ObjValueInternals {
 	super_obj: Option<ObjValue>,
-	this_entries: Rc<BTreeMap<String, ObjMember>>,
-	value_cache: RefCell<HashMap<String, Val>>,
+	this_entries: Rc<BTreeMap<Rc<str>, ObjMember>>,
+	value_cache: RefCell<HashMap<Rc<str>, Val>>,
 }
+#[derive(Clone)]
 pub struct ObjValue(pub(crate) Rc<ObjValueInternals>);
 impl Debug for ObjValue {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -43,7 +44,7 @@
 impl ObjValue {
 	pub fn new(
 		super_obj: Option<ObjValue>,
-		this_entries: Rc<BTreeMap<String, ObjMember>>,
+		this_entries: Rc<BTreeMap<Rc<str>, ObjMember>>,
 	) -> ObjValue {
 		ObjValue(Rc::new(ObjValueInternals {
 			super_obj,
@@ -57,7 +58,7 @@
 			Some(v) => ObjValue::new(Some(v.with_super(super_obj)), self.0.this_entries.clone()),
 		}
 	}
-	pub fn enum_fields(&self, handler: &impl Fn(&str, &Visibility)) {
+	pub fn enum_fields(&self, handler: &impl Fn(&Rc<str>, &Visibility)) {
 		if let Some(s) = &self.0.super_obj {
 			s.enum_fields(handler);
 		}
@@ -65,7 +66,7 @@
 			handler(&name, &member.visibility);
 		}
 	}
-	pub fn fields_visibility(&self) -> IndexMap<String, bool> {
+	pub fn fields_visibility(&self) -> IndexMap<Rc<str>, bool> {
 		let out = Rc::new(RefCell::new(IndexMap::new()));
 		self.enum_fields(&|name, visibility| {
 			let mut out = out.borrow_mut();
@@ -85,16 +86,20 @@
 		});
 		Rc::try_unwrap(out).unwrap().into_inner()
 	}
-	pub fn get(&self, key: &str) -> Result<Option<Val>> {
-		if let Some(v) = self.0.value_cache.borrow().get(key) {
+	pub fn visible_fields(&self) -> Vec<Rc<str>> {
+		self.fields_visibility()
+			.into_iter()
+			.filter(|(_k, v)| *v)
+			.map(|(k, _)| k)
+			.collect()
+	}
+	pub fn get(&self, key: Rc<str>) -> Result<Option<Val>> {
+		if let Some(v) = self.0.value_cache.borrow().get(&key) {
 			return Ok(Some(v.clone()));
 		}
-		if let Some(v) = self.get_raw(key, self)? {
+		if let Some(v) = self.get_raw(&key, self)? {
 			let v = v.unwrap_if_lazy()?;
-			self.0
-				.value_cache
-				.borrow_mut()
-				.insert(key.to_owned(), v.clone());
+			self.0.value_cache.borrow_mut().insert(key, v.clone());
 			Ok(Some(v))
 		} else {
 			Ok(None)
@@ -108,10 +113,10 @@
 					.evaluate()?,
 			)),
 			(Some(k), Some(s)) => {
-				let our = k
+				let lazy = k
 					.invoke
-					.evaluate(Some(real_this.clone()), self.0.super_obj.clone())?
-					.evaluate()?;
+					.evaluate(Some(real_this.clone()), self.0.super_obj.clone())?;
+				let our = lazy.evaluate()?;
 				if k.add {
 					s.get_raw(key, real_this)?
 						.map_or(Ok(Some(our.clone())), |v| {
@@ -124,11 +129,6 @@
 			(None, Some(s)) => s.get_raw(key, real_this),
 			(None, None) => Ok(None),
 		}
-	}
-}
-impl Clone for ObjValue {
-	fn clone(&self) -> Self {
-		ObjValue(self.0.clone())
 	}
 }
 impl PartialEq for ObjValue {
modifiedcrates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -81,7 +81,7 @@
 	}
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy, PartialEq)]
 pub enum ValType {
 	Bool,
 	Null,
@@ -115,42 +115,46 @@
 pub enum Val {
 	Bool(bool),
 	Null,
-	Str(String),
+	Str(Rc<str>),
 	Num(f64),
 	Lazy(LazyVal),
-	Arr(Vec<Val>),
+	Arr(Rc<Vec<Val>>),
 	Obj(ObjValue),
 	Func(FuncDesc),
 
 	// Library functions implemented in native
-	Intristic(String, String),
+	Intristic(Rc<str>, Rc<str>),
+}
+macro_rules! matches_unwrap {
+	($e: expr, $p: pat, $r: expr) => {
+		match $e {
+			$p => $r,
+			_ => panic!("no match"),
+			}
+	};
 }
 impl Val {
+	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {
+		let this_type = self.value_type()?;
+		if this_type != val_type {
+			create_error(Error::TypeMismatch(context, vec![val_type], this_type))
+		} else {
+			Ok(())
+		}
+	}
 	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {
-		match self.unwrap_if_lazy()? {
-			Val::Bool(v) => Ok(v),
-			v => create_error(Error::TypeMismatch(
-				context,
-				vec![ValType::Bool],
-				v.value_type()?,
-			)),
-		}
+		self.assert_type(context, ValType::Bool)?;
+		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))
 	}
-	pub fn try_cast_str(self, context: &'static str) -> Result<String> {
-		match self.unwrap_if_lazy()? {
-			Val::Str(v) => Ok(v),
-			v => create_error(Error::TypeMismatch(
-				context,
-				vec![ValType::Str],
-				v.value_type()?,
-			)),
-		}
+	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {
+		self.assert_type(context, ValType::Str)?;
+		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))
 	}
-	pub fn unwrap_if_lazy(self) -> Result<Self> {
+	pub fn unwrap_if_lazy(&self) -> Result<Self> {
 		Ok(if let Val::Lazy(v) = self {
 			v.evaluate()?.unwrap_if_lazy()?
 		} else {
-			self
+			self.clone()
 		})
 	}
 	pub fn value_type(&self) -> Result<ValType> {
@@ -166,26 +170,30 @@
 			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,
 		})
 	}
-	pub fn into_json(self, padding: usize) -> Result<String> {
+	pub fn into_json(self, padding: usize) -> Result<Rc<str>> {
 		with_state(|s| {
 			let ctx = s
 				.create_default_context()?
-				.with_var("__tmp__to_json__".to_owned(), self)?;
-			if let Val::Str(result) = evaluate(
+				.with_var("__tmp__to_json__".into(), self)?;
+			Ok(evaluate(
 				ctx,
 				&el!(Expr::Apply(
 					el!(Expr::Index(
-						el!(Expr::Var("std".to_owned())),
-						el!(Expr::Str("manifestJsonEx".to_owned()))
+						el!(Expr::Var("std".into())),
+						el!(Expr::Str("manifestJsonEx".into()))
 					)),
 					ArgsDesc(vec![
-						Arg(None, el!(Expr::Var("__tmp__to_json__".to_owned()))),
-						Arg(None, el!(Expr::Str(" ".repeat(padding))))
+						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),
+						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))
 					]),
 					false
 				)),
-			)? {
-				Ok(result)
+			)?
+			.try_cast_str("to json")?)
+		})
+	}
+}
+
 			} else {
 				unreachable!()
 			}
modifiedcrates/jsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jsonnet-parser/src/expr.rs
+++ b/crates/jsonnet-parser/src/expr.rs
@@ -1,15 +1,15 @@
 use serde::{Deserialize, Serialize};
 use std::{fmt::Debug, ops::Deref, path::PathBuf, rc::Rc};
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub enum FieldName {
 	/// {fixed: 2}
-	Fixed(String),
+	Fixed(Rc<str>),
 	/// {["dyn"+"amic"]: 3}
 	Dyn(LocExpr),
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
 pub enum Visibility {
 	/// :
 	Normal,
@@ -19,10 +19,10 @@
 	Unhide,
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct FieldMember {
 	pub name: FieldName,
 	pub plus: bool,
@@ -31,7 +31,7 @@
 	pub value: LocExpr,
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub enum Member {
 	Field(FieldMember),
 	BindStmt(BindSpec),
@@ -71,11 +71,11 @@
 }
 
 /// name, default value
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct Param(pub String, pub Option<LocExpr>);
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct Param(pub Rc<str>, pub Option<LocExpr>);
 /// Defined function parameters
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ParamsDesc(pub Vec<Param>);
+pub struct ParamsDesc(pub Rc<Vec<Param>>);
 impl Deref for ParamsDesc {
 	type Target = Vec<Param>;
 	fn deref(&self) -> &Self::Target {
@@ -83,9 +83,9 @@
 	}
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct Arg(pub Option<String>, pub LocExpr);
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct ArgsDesc(pub Vec<Arg>);
 impl Deref for ArgsDesc {
 	type Target = Vec<Arg>;
@@ -96,35 +96,38 @@
 
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct BindSpec {
-	pub name: String,
+	pub name: Rc<str>,
 	pub params: Option<ParamsDesc>,
 	pub value: LocExpr,
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct IfSpecData(pub LocExpr);
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct ForSpecData(pub String, pub LocExpr);
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct ForSpecData(pub Rc<str>, pub LocExpr);
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub enum CompSpec {
 	IfSpec(IfSpecData),
 	ForSpec(ForSpecData),
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct ObjComp {
+	pub pre_locals: Vec<BindSpec>,
+	pub key: LocExpr,
+	pub value: LocExpr,
+	pub post_locals: Vec<BindSpec>,
+	pub compspecs: Vec<CompSpec>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub enum ObjBody {
 	MemberList(Vec<Member>),
-	ObjComp {
-		pre_locals: Vec<BindSpec>,
-		key: LocExpr,
-		value: LocExpr,
-		post_locals: Vec<BindSpec>,
-		compspecs: Vec<CompSpec>,
-	},
+	ObjComp(ObjComp),
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy)]
 pub enum LiteralType {
 	This,
 	Super,
@@ -134,7 +137,7 @@
 	False,
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct SliceDesc {
 	pub start: Option<LocExpr>,
 	pub end: Option<LocExpr>,
@@ -142,16 +145,16 @@
 }
 
 /// Syntax base
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub enum Expr {
 	Literal(LiteralType),
 
 	/// String value: "hello"
-	Str(String),
+	Str(Rc<str>),
 	/// Number: 1, 2.0, 2e+20
 	Num(f64),
 	/// Variable name: test
-	Var(String),
+	Var(Rc<str>),
 
 	/// Array of expressions: [1, 2, "Hello"]
 	Arr(Vec<LocExpr>),
modifiedcrates/jsonnet-parser/src/lib.rsdiffbeforeafterboth
before · crates/jsonnet-parser/src/lib.rs
1#![feature(box_syntax)]2#![feature(test)]34extern crate test;56use peg::parser;7use std::{path::PathBuf, rc::Rc};8mod expr;9pub use expr::*;10pub use peg;1112pub struct ParserSettings {13	pub loc_data: bool,14	pub file_name: Rc<PathBuf>,15}1617parser! {18	grammar jsonnet_parser() for str {19		use peg::ParseLiteral;2021		/// Standard C-like comments22		rule comment()23			= "//" (!['\n'][_])* "\n"24			/ "/*" ("\\*/" / "\\\\" / (!("*/")[_]))* "*/"25			/ "#" (!['\n'][_])* "\n"2627		rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")28		rule _() = single_whitespace()*2930		/// For comma-delimited elements31		rule comma() = quiet!{_ "," _} / expected!("<comma>")32		rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}33		rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}34		rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']35		/// Sequence of digits36		rule uint() -> u64 = a:$(digit()+) { a.parse().unwrap() }37		/// Number in scientific notation format38		rule number() -> f64 = quiet!{a:$(uint() ("." uint())? (['e'|'E'] (s:['+'|'-'])? uint())?) { a.parse().unwrap() }} / expected!("<number>")3940		/// Reserved word followed by any non-alphanumberic41		rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()42		rule id() -> String = quiet!{ !reserved() s:$(alpha() (alpha() / digit())*) {s.to_owned()}} / expected!("<identifier>")4344		rule keyword(id: &'static str)45			= ##parse_string_literal(id) end_of_ident()46		// Adds location data information to existing expression47		rule l(s: &ParserSettings, x: rule<Expr>) -> LocExpr48			= start:position!() v:x() end:position!() {loc_expr!(v, s.loc_data, (s.file_name.clone(), start, end))}4950		pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) }51		pub rule params(s: &ParserSettings) -> expr::ParamsDesc52			= params:(param(s) ** comma()) {53				let mut defaults_started = false;54				for param in &params {55					defaults_started = defaults_started || param.1.is_some();56					assert_eq!(defaults_started, param.1.is_some(), "defauld parameters should be used after all positionals");57				}58				expr::ParamsDesc(params)59			}60			/ { expr::ParamsDesc(Vec::new()) }6162		pub rule arg(s: &ParserSettings) -> expr::Arg63			= name:id() _ "=" _ expr:expr(s) {expr::Arg(Some(name), expr)}64			/ expr:expr(s) {expr::Arg(None, expr)}65		pub rule args(s: &ParserSettings) -> expr::ArgsDesc66			= args:arg(s) ** comma() comma()? {67				let mut named_started = false;68				for arg in &args {69					named_started = named_started || arg.0.is_some();70					assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals");71				}72				expr::ArgsDesc(args)73			}74			/ { expr::ArgsDesc(Vec::new()) }7576		pub rule bind(s: &ParserSettings) -> expr::BindSpec77			= name:id() _ "=" _ expr:expr(s) {expr::BindSpec{name, params: None, value: expr}}78			/ name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name, params: Some(params), value: expr}}79		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt80			= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }8182		pub rule whole_line() -> &'input str83			= str:$((!['\n'][_])* "\n") {str}84		pub rule string_block() -> String85			= "|||" (!['\n']single_whitespace())* "\n"86			  prefix:[' ']+ first_line:whole_line()87			  lines:([' ']*<{prefix.len()}> s:whole_line() {s})*88			  [' ']*<, {prefix.len() - 1}> "|||"89			  {let mut l = first_line.to_owned(); l.extend(lines); l}90		pub rule string() -> String91			= "\"" str:$(("\\\"" / "\\\\" / (!['"'][_]))*) "\"" {unescape::unescape(str).unwrap()}92			/ "'" str:$(("\\'" / "\\\\" / (!['\''][_]))*) "'" {unescape::unescape(str).unwrap()}93			/ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}94			/ "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}95			/ string_block()9697		pub rule field_name(s: &ParserSettings) -> expr::FieldName98			= name:id() {expr::FieldName::Fixed(name)}99			/ name:string() {expr::FieldName::Fixed(name)}100			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}101		pub rule visibility() -> expr::Visibility102			= ":::" {expr::Visibility::Unhide}103			/ "::" {expr::Visibility::Hidden}104			/ ":" {expr::Visibility::Normal}105		pub rule field(s: &ParserSettings) -> expr::FieldMember106			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{107				name,108				plus: plus.is_some(),109				params: None,110				visibility,111				value,112			}}113			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{114				name,115				plus: false,116				params: Some(params),117				visibility,118				value,119			}}120		pub rule obj_local(s: &ParserSettings) -> BindSpec121			= keyword("local") _ bind:bind(s) {bind}122		pub rule member(s: &ParserSettings) -> expr::Member123			= bind:obj_local(s) {expr::Member::BindStmt(bind)}124			/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}125			/ field:field(s) {expr::Member::Field(field)}126		pub rule objinside(s: &ParserSettings) -> expr::ObjBody127			= pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {128				expr::ObjBody::ObjComp {129					pre_locals,130					key,131					value,132					post_locals,133					compspecs: [vec![CompSpec::ForSpec(forspec)], others.unwrap_or_default()].concat(),134				}135			}136			/ members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}137		pub rule ifspec(s: &ParserSettings) -> IfSpecData138			= keyword("if") _ expr:expr(s) {IfSpecData(expr)}139		pub rule forspec(s: &ParserSettings) -> ForSpecData140			= keyword("for") _ id:id() _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}141		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>142			= s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}143		pub rule local_expr(s: &ParserSettings) -> LocExpr144			= l(s,<keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }>)145		pub rule string_expr(s: &ParserSettings) -> LocExpr146			= l(s, <s:string() {Expr::Str(s)}>)147		pub rule obj_expr(s: &ParserSettings) -> LocExpr148			= l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>)149		pub rule array_expr(s: &ParserSettings) -> LocExpr150			= l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>)151		pub rule array_comp_expr(s: &ParserSettings) -> LocExpr152			= l(s,<"[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {Expr::ArrComp(expr, [vec![CompSpec::ForSpec(forspec)], others.unwrap_or_default()].concat())}>)153		pub rule number_expr(s: &ParserSettings) -> LocExpr154			= l(s,<n:number() { expr::Expr::Num(n) }>)155		pub rule var_expr(s: &ParserSettings) -> LocExpr156			= l(s,<n:id() { expr::Expr::Var(n) }>)157		pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr158			= l(s,<cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{159				cond,160				cond_then,161				cond_else,162			}}>)163164		pub rule literal(s: &ParserSettings) -> LocExpr165			= l(s,<v:(166				keyword("null") {LiteralType::Null}167				/ keyword("true") {LiteralType::True}168				/ keyword("false") {LiteralType::False}169				/ keyword("self") {LiteralType::This}170				/ keyword("$") {LiteralType::Dollar}171				/ keyword("super") {LiteralType::Super}172			) {Expr::Literal(v)}>)173174		pub rule expr_basic(s: &ParserSettings) -> LocExpr175			= literal(s)176177			/ string_expr(s) / number_expr(s)178			/ array_expr(s)179			/ obj_expr(s)180			/ array_expr(s)181			/ array_comp_expr(s)182183			/ l(s,<keyword("importstr") _ path:string() {Expr::ImportStr(PathBuf::from(path))}>)184			/ l(s,<keyword("import") _ path:string() {Expr::Import(PathBuf::from(path))}>)185186			/ var_expr(s)187			/ local_expr(s)188			/ if_then_else_expr(s)189190			/ l(s,<keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}>)191			/ l(s,<assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }>)192193			/ l(s,<keyword("error") _ expr:expr(s) { Expr::Error(expr) }>)194195		rule slice_part(s: &ParserSettings) -> Option<LocExpr>196			= e:(_ e:expr(s) _{e})? {e}197		pub rule slice_desc(s: &ParserSettings) -> SliceDesc198			= start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {199				let (end, step) = if let Some((end, step)) = pair {200					(end, step)201				}else{202					(None, None)203				};204205				SliceDesc { start, end, step }206			}207208		rule expr(s: &ParserSettings) -> LocExpr209			= start:position!() a:precedence! {210				a:(@) _ "||" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Or, b))}211				--212				a:(@) _ "&&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::And, b))}213				--214				a:(@) _ "|" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitOr, b))}215				--216				a:@ _ "^" _ b:(@) {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitXor, b))}217				--218				a:(@) _ "&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitAnd, b))}219				--220				a:(@) _ "==" _ b:@ {loc_expr_todo!(Expr::Apply(221					el!(Expr::Index(222						el!(Expr::Var("std".to_owned())),223						el!(Expr::Str("equals".to_owned()))224					)),225					ArgsDesc(vec![Arg(None, a), Arg(None, b)]),226					true227				))}228				a:(@) _ "!=" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, el!(Expr::Apply(229					el!(Expr::Index(230						el!(Expr::Var("std".to_owned())),231						el!(Expr::Str("equals".to_owned()))232					)),233					ArgsDesc(vec![Arg(None, a), Arg(None, b)]),234					true235				))))}236				--237				a:(@) _ "<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lt, b))}238				a:(@) _ ">" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gt, b))}239				a:(@) _ "<=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lte, b))}240				a:(@) _ ">=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gte, b))}241				a:(@) _ keyword("in") _ b:@ {loc_expr_todo!(Expr::Apply(242					el!(Expr::Index(243						el!(Expr::Var("std".to_owned())),244						el!(Expr::Str("objectHasEx".to_owned()))245					)), ArgsDesc(vec![Arg(None, b), Arg(None, a), Arg(None, el!(Expr::Literal(LiteralType::True)))]),246					true247				))}248				--249				a:(@) _ "<<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lhs, b))}250				a:(@) _ ">>" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Rhs, b))}251				--252				a:(@) _ "+" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Add, b))}253				a:(@) _ "-" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Sub, b))}254				--255				a:(@) _ "*" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mul, b))}256				a:(@) _ "/" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Div, b))}257				a:(@) _ "%" _ b:@ {loc_expr_todo!(Expr::Apply(258					el!(Expr::Index(259						el!(Expr::Var("std".to_owned())),260						el!(Expr::Str("mod".to_owned()))261					)), ArgsDesc(vec![Arg(None, a), Arg(None, b)]),262					true263				))}264				--265						"-" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Minus, b))}266						"!" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, b))}267						"~" _ b:@ { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::BitNot, b)) }268				--269				a:(@) _ "[" _ s:slice_desc(s) _ "]" {loc_expr_todo!(Expr::Apply(270					el!(Expr::Index(271						el!(Expr::Var("std".to_owned())),272						el!(Expr::Str("slice".to_owned())),273					)),274					ArgsDesc(vec![275						Arg(None, a),276						Arg(None, s.start.unwrap_or_else(||el!(Expr::Literal(LiteralType::Null)))),277						Arg(None, s.end.unwrap_or_else(||el!(Expr::Literal(LiteralType::Null)))),278						Arg(None, s.step.unwrap_or_else(||el!(Expr::Literal(LiteralType::Null)))),279					]),280					true,281				))}282				a:(@) _ "." _ s:id() {loc_expr_todo!(Expr::Index(a, el!(Expr::Str(s))))}283				a:(@) _ "[" _ s:expr(s) _ "]" {loc_expr_todo!(Expr::Index(a, s))}284				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {loc_expr_todo!(Expr::Apply(a, args, ts.is_some()))}285				a:(@) _ "{" _ body:objinside(s) _ "}" {loc_expr_todo!(Expr::ObjExtend(a, body))}286				--287				e:expr_basic(s) {e}288				"(" _ e:expr(s) _ ")" {loc_expr_todo!(Expr::Parened(e))}289			} end:position!() {290				let LocExpr(e, _) = a;291				LocExpr(e, if s.loc_data {292					Some(ExprLocation(s.file_name.clone(), start, end))293				} else {294					None295				})296			}297			/ e:expr_basic(s) {e}298299		pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}300	}301}302303pub type ParseError = peg::error::ParseError<peg::str::LineCol>;304pub fn parse(str: &str, settings: &ParserSettings) -> Result<LocExpr, ParseError> {305	jsonnet_parser::jsonnet(str, settings)306}307308#[macro_export]309macro_rules! el {310	($expr:expr) => {311		LocExpr(std::rc::Rc::new($expr), None)312	};313}314315#[cfg(test)]316pub mod tests {317	use super::{expr::*, parse};318	use crate::ParserSettings;319	use std::path::PathBuf;320	use std::rc::Rc;321322	macro_rules! parse {323		($s:expr) => {324			parse(325				$s,326				&ParserSettings {327					loc_data: false,328					file_name: Rc::new(PathBuf::from("/test.jsonnet")),329					},330				)331			.unwrap()332		};333	}334335	mod expressions {336		use super::*;337338		pub fn basic_math() -> LocExpr {339			el!(Expr::BinaryOp(340				el!(Expr::Num(2.0)),341				BinaryOpType::Add,342				el!(Expr::BinaryOp(343					el!(Expr::Num(2.0)),344					BinaryOpType::Mul,345					el!(Expr::Num(2.0)),346				)),347			))348		}349	}350351	#[test]352	fn multiline_string() {353		assert_eq!(354			parse!("|||\n    Hello world!\n     a\n|||"),355			el!(Expr::Str("Hello world!\n a\n".to_owned())),356		)357	}358359	#[test]360	fn slice() {361		parse!("a[1:]");362		parse!("a[1::]");363		parse!("a[:1:]");364		parse!("a[::1]");365		parse!("str[:len - 1]");366	}367368	#[test]369	fn string_escaping() {370		assert_eq!(371			parse!(r#""Hello, \"world\"!""#),372			el!(Expr::Str(r#"Hello, "world"!"#.to_owned())),373		);374		assert_eq!(375			parse!(r#"'Hello \'world\'!'"#),376			el!(Expr::Str("Hello 'world'!".to_owned())),377		);378		assert_eq!(parse!(r#"'\\\\'"#), el!(Expr::Str("\\\\".to_owned())),);379	}380381	#[test]382	fn string_unescaping() {383		assert_eq!(384			parse!(r#""Hello\nWorld""#),385			el!(Expr::Str("Hello\nWorld".to_owned())),386		);387	}388389	#[test]390	fn string_verbantim() {391		assert_eq!(392			parse!(r#"@"Hello\n""World""""#),393			el!(Expr::Str("Hello\\n\"World\"".to_owned())),394		);395	}396397	#[test]398	fn imports() {399		assert_eq!(400			parse!("import \"hello\""),401			el!(Expr::Import(PathBuf::from("hello"))),402		);403		assert_eq!(404			parse!("importstr \"garnish.txt\""),405			el!(Expr::ImportStr(PathBuf::from("garnish.txt")))406		);407	}408409	#[test]410	fn empty_object() {411		assert_eq!(parse!("{}"), el!(Expr::Obj(ObjBody::MemberList(vec![]))));412	}413414	#[test]415	fn basic_math() {416		assert_eq!(417			parse!("2+2*2"),418			el!(Expr::BinaryOp(419				el!(Expr::Num(2.0)),420				BinaryOpType::Add,421				el!(Expr::BinaryOp(422					el!(Expr::Num(2.0)),423					BinaryOpType::Mul,424					el!(Expr::Num(2.0))425				))426			))427		);428	}429430	#[test]431	fn basic_math_with_indents() {432		assert_eq!(parse!("2	+ 	  2	  *	2   	"), expressions::basic_math());433	}434435	#[test]436	fn basic_math_parened() {437		assert_eq!(438			parse!("2+(2+2*2)"),439			el!(Expr::BinaryOp(440				el!(Expr::Num(2.0)),441				BinaryOpType::Add,442				el!(Expr::Parened(expressions::basic_math())),443			))444		);445	}446447	/// Comments should not affect parsing448	#[test]449	fn comments() {450		assert_eq!(451			parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),452			el!(Expr::BinaryOp(453				el!(Expr::Num(2.0)),454				BinaryOpType::Add,455				el!(Expr::BinaryOp(456					el!(Expr::Num(3.0)),457					BinaryOpType::Mul,458					el!(Expr::Num(4.0))459				))460			))461		);462	}463464	/// Comments should be able to be escaped465	#[test]466	fn comment_escaping() {467		assert_eq!(468			parse!("2/*\\*/+*/ - 22"),469			el!(Expr::BinaryOp(470				el!(Expr::Num(2.0)),471				BinaryOpType::Sub,472				el!(Expr::Num(22.0))473			))474		);475	}476477	#[test]478	fn suffix() {479		// assert_eq!(parse!("std.test"), el!(Expr::Num(2.2)));480		// assert_eq!(parse!("std(2)"), el!(Expr::Num(2.2)));481		// assert_eq!(parse!("std.test(2)"), el!(Expr::Num(2.2)));482		// assert_eq!(parse!("a[b]"), el!(Expr::Num(2.2)))483	}484485	#[test]486	fn array_comp() {487		use Expr::*;488		assert_eq!(489			parse!("[std.deepJoin(x) for x in arr]"),490			el!(ArrComp(491				el!(Apply(492					el!(Index(493						el!(Var("std".to_owned())),494						el!(Str("deepJoin".to_owned()))495					)),496					ArgsDesc(vec![Arg(None, el!(Var("x".to_owned())))]),497					false,498				)),499				vec![CompSpec::ForSpec(ForSpecData(500					"x".to_owned(),501					el!(Var("arr".to_owned()))502				))]503			)),504		)505	}506507	#[test]508	fn reserved() {509		use Expr::*;510		assert_eq!(parse!("null"), el!(Literal(LiteralType::Null)));511		assert_eq!(parse!("nulla"), el!(Var("nulla".to_owned())));512	}513514	#[test]515	fn multiple_args_buf() {516		parse!("a(b, null_fields)");517	}518519	#[test]520	fn infix_precedence() {521		use Expr::*;522		assert_eq!(523			parse!("!a && !b"),524			el!(BinaryOp(525				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".to_owned())))),526				BinaryOpType::And,527				el!(UnaryOp(UnaryOpType::Not, el!(Var("b".to_owned()))))528			))529		);530	}531532	#[test]533	fn infix_precedence_division() {534		use Expr::*;535		assert_eq!(536			parse!("!a / !b"),537			el!(BinaryOp(538				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".to_owned())))),539				BinaryOpType::Div,540				el!(UnaryOp(UnaryOpType::Not, el!(Var("b".to_owned()))))541			))542		);543	}544545	#[test]546	fn double_negation() {547		use Expr::*;548		assert_eq!(549			parse!("!!a"),550			el!(UnaryOp(551				UnaryOpType::Not,552				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".to_owned()))))553			))554		)555	}556557	#[test]558	fn array_test_error() {559		parse!("[a for a in b if c for e in f]");560		//                    ^^^^ failed code561	}562563	#[test]564	fn can_parse_stdlib() {565		parse!(jsonnet_stdlib::STDLIB_STR);566	}567568	use test::Bencher;569570	// From source code571	#[bench]572	fn bench_parse_peg(b: &mut Bencher) {573		b.iter(|| parse!(jsonnet_stdlib::STDLIB_STR))574	}575576	// From serialized blob577	#[bench]578	fn bench_parse_serde_bincode(b: &mut Bencher) {579		let serialized = bincode::serialize(&parse!(jsonnet_stdlib::STDLIB_STR)).unwrap();580		b.iter(|| bincode::deserialize::<LocExpr>(&serialized))581	}582}
after · crates/jsonnet-parser/src/lib.rs
1#![feature(box_syntax)]2#![feature(test)]34extern crate test;56use peg::parser;7use std::{path::PathBuf, rc::Rc};8mod expr;9pub use expr::*;10pub use peg;1112pub struct ParserSettings {13	pub loc_data: bool,14	pub file_name: Rc<PathBuf>,15}1617parser! {18	grammar jsonnet_parser() for str {19		use peg::ParseLiteral;2021		/// Standard C-like comments22		rule comment()23			= "//" (!['\n'][_])* "\n"24			/ "/*" ("\\*/" / "\\\\" / (!("*/")[_]))* "*/"25			/ "#" (!['\n'][_])* "\n"2627		rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")28		rule _() = single_whitespace()*2930		/// For comma-delimited elements31		rule comma() = quiet!{_ "," _} / expected!("<comma>")32		rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}33		rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}34		rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']35		/// Sequence of digits36		rule uint() -> u64 = a:$(digit()+) { a.parse().unwrap() }37		/// Number in scientific notation format38		rule number() -> f64 = quiet!{a:$(uint() ("." uint())? (['e'|'E'] (s:['+'|'-'])? uint())?) { a.parse().unwrap() }} / expected!("<number>")3940		/// Reserved word followed by any non-alphanumberic41		rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()42		rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")4344		rule keyword(id: &'static str)45			= ##parse_string_literal(id) end_of_ident()46		// Adds location data information to existing expression47		rule l(s: &ParserSettings, x: rule<Expr>) -> LocExpr48			= start:position!() v:x() end:position!() {loc_expr!(v, s.loc_data, (s.file_name.clone(), start, end))}4950		pub rule param(s: &ParserSettings) -> expr::Param = name:$(id()) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name.into(), expr) }51		pub rule params(s: &ParserSettings) -> expr::ParamsDesc52			= params:(param(s) ** comma()) {53				let mut defaults_started = false;54				for param in &params {55					defaults_started = defaults_started || param.1.is_some();56					assert_eq!(defaults_started, param.1.is_some(), "defauld parameters should be used after all positionals");57				}58				expr::ParamsDesc(Rc::new(params))59			}60			/ { expr::ParamsDesc(Rc::new(Vec::new())) }6162		pub rule arg(s: &ParserSettings) -> expr::Arg63			= name:$(id()) _ "=" _ expr:expr(s) {expr::Arg(Some(name.into()), expr)}64			/ expr:expr(s) {expr::Arg(None, expr)}65		pub rule args(s: &ParserSettings) -> expr::ArgsDesc66			= args:arg(s) ** comma() comma()? {67				let mut named_started = false;68				for arg in &args {69					named_started = named_started || arg.0.is_some();70					assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals");71				}72				expr::ArgsDesc(args)73			}74			/ { expr::ArgsDesc(Vec::new()) }7576		pub rule bind(s: &ParserSettings) -> expr::BindSpec77			= name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}}78			/ name:$(id()) _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: Some(params), value: expr}}79		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt80			= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }8182		pub rule whole_line() -> &'input str83			= str:$((!['\n'][_])* "\n") {str}84		pub rule string_block() -> String85			= "|||" (!['\n']single_whitespace())* "\n"86			  prefix:[' ']+ first_line:whole_line()87			  lines:([' ']*<{prefix.len()}> s:whole_line() {s})*88			  [' ']*<, {prefix.len() - 1}> "|||"89			  {let mut l = first_line.to_owned(); l.extend(lines); l}90		pub rule string() -> String91			= "\"" str:$(("\\\"" / "\\\\" / (!['"'][_]))*) "\"" {unescape::unescape(str).unwrap()}92			/ "'" str:$(("\\'" / "\\\\" / (!['\''][_]))*) "'" {unescape::unescape(str).unwrap()}93			/ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}94			/ "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}95			/ string_block()9697		pub rule field_name(s: &ParserSettings) -> expr::FieldName98			= name:$(id()) {expr::FieldName::Fixed(name.into())}99			/ name:string() {expr::FieldName::Fixed(name.into())}100			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}101		pub rule visibility() -> expr::Visibility102			= ":::" {expr::Visibility::Unhide}103			/ "::" {expr::Visibility::Hidden}104			/ ":" {expr::Visibility::Normal}105		pub rule field(s: &ParserSettings) -> expr::FieldMember106			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{107				name,108				plus: plus.is_some(),109				params: None,110				visibility,111				value,112			}}113			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{114				name,115				plus: false,116				params: Some(params),117				visibility,118				value,119			}}120		pub rule obj_local(s: &ParserSettings) -> BindSpec121			= keyword("local") _ bind:bind(s) {bind}122		pub rule member(s: &ParserSettings) -> expr::Member123			= bind:obj_local(s) {expr::Member::BindStmt(bind)}124			/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}125			/ field:field(s) {expr::Member::Field(field)}126		pub rule objinside(s: &ParserSettings) -> expr::ObjBody127			= pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {128				let mut compspecs = vec![CompSpec::ForSpec(forspec)];129				compspecs.extend(others.unwrap_or_default());130				expr::ObjBody::ObjComp(expr::ObjComp{131					pre_locals,132					key,133					value,134					post_locals,135					compspecs,136				})137			}138			/ members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}139		pub rule ifspec(s: &ParserSettings) -> IfSpecData140			= keyword("if") _ expr:expr(s) {IfSpecData(expr)}141		pub rule forspec(s: &ParserSettings) -> ForSpecData142			= keyword("for") _ id:$(id()) _ keyword("in") _ cond:expr(s) {ForSpecData(id.into(), cond)}143		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>144			= s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}145		pub rule local_expr(s: &ParserSettings) -> LocExpr146			= l(s,<keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }>)147		pub rule string_expr(s: &ParserSettings) -> LocExpr148			= l(s, <s:string() {Expr::Str(s.into())}>)149		pub rule obj_expr(s: &ParserSettings) -> LocExpr150			= l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>)151		pub rule array_expr(s: &ParserSettings) -> LocExpr152			= l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>)153		pub rule array_comp_expr(s: &ParserSettings) -> LocExpr154			= l(s,<"[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {155				let mut specs = vec![CompSpec::ForSpec(forspec)];156				specs.extend(others.unwrap_or_default());157				Expr::ArrComp(expr, specs)158			}>)159		pub rule number_expr(s: &ParserSettings) -> LocExpr160			= l(s,<n:number() { expr::Expr::Num(n) }>)161		pub rule var_expr(s: &ParserSettings) -> LocExpr162			= l(s,<n:$(id()) { expr::Expr::Var(n.into()) }>)163		pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr164			= l(s,<cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{165				cond,166				cond_then,167				cond_else,168			}}>)169170		pub rule literal(s: &ParserSettings) -> LocExpr171			= l(s,<v:(172				keyword("null") {LiteralType::Null}173				/ keyword("true") {LiteralType::True}174				/ keyword("false") {LiteralType::False}175				/ keyword("self") {LiteralType::This}176				/ keyword("$") {LiteralType::Dollar}177				/ keyword("super") {LiteralType::Super}178			) {Expr::Literal(v)}>)179180		pub rule expr_basic(s: &ParserSettings) -> LocExpr181			= literal(s)182183			/ string_expr(s) / number_expr(s)184			/ array_expr(s)185			/ obj_expr(s)186			/ array_expr(s)187			/ array_comp_expr(s)188189			/ l(s,<keyword("importstr") _ path:string() {Expr::ImportStr(PathBuf::from(path))}>)190			/ l(s,<keyword("import") _ path:string() {Expr::Import(PathBuf::from(path))}>)191192			/ var_expr(s)193			/ local_expr(s)194			/ if_then_else_expr(s)195196			/ l(s,<keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}>)197			/ l(s,<assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }>)198199			/ l(s,<keyword("error") _ expr:expr(s) { Expr::Error(expr) }>)200201		rule slice_part(s: &ParserSettings) -> Option<LocExpr>202			= e:(_ e:expr(s) _{e})? {e}203		pub rule slice_desc(s: &ParserSettings) -> SliceDesc204			= start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {205				let (end, step) = if let Some((end, step)) = pair {206					(end, step)207				}else{208					(None, None)209				};210211				SliceDesc { start, end, step }212			}213214		rule expr(s: &ParserSettings) -> LocExpr215			= start:position!() a:precedence! {216				a:(@) _ "||" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Or, b))}217				--218				a:(@) _ "&&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::And, b))}219				--220				a:(@) _ "|" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitOr, b))}221				--222				a:@ _ "^" _ b:(@) {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitXor, b))}223				--224				a:(@) _ "&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitAnd, b))}225				--226				a:(@) _ "==" _ b:@ {loc_expr_todo!(Expr::Apply(227					el!(Expr::Index(228						el!(Expr::Var("std".into())),229						el!(Expr::Str("equals".into()))230					)),231					ArgsDesc(vec![Arg(None, a), Arg(None, b)]),232					true233				))}234				a:(@) _ "!=" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, el!(Expr::Apply(235					el!(Expr::Index(236						el!(Expr::Var("std".into())),237						el!(Expr::Str("equals".into()))238					)),239					ArgsDesc(vec![Arg(None, a), Arg(None, b)]),240					true241				))))}242				--243				a:(@) _ "<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lt, b))}244				a:(@) _ ">" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gt, b))}245				a:(@) _ "<=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lte, b))}246				a:(@) _ ">=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gte, b))}247				a:(@) _ keyword("in") _ b:@ {loc_expr_todo!(Expr::Apply(248					el!(Expr::Index(249						el!(Expr::Var("std".into())),250						el!(Expr::Str("objectHasEx".into()))251					)), ArgsDesc(vec![Arg(None, b), Arg(None, a), Arg(None, el!(Expr::Literal(LiteralType::True)))]),252					true253				))}254				--255				a:(@) _ "<<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lhs, b))}256				a:(@) _ ">>" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Rhs, b))}257				--258				a:(@) _ "+" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Add, b))}259				a:(@) _ "-" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Sub, b))}260				--261				a:(@) _ "*" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mul, b))}262				a:(@) _ "/" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Div, b))}263				a:(@) _ "%" _ b:@ {loc_expr_todo!(Expr::Apply(264					el!(Expr::Index(265						el!(Expr::Var("std".into())),266						el!(Expr::Str("mod".into()))267					)), ArgsDesc(vec![Arg(None, a), Arg(None, b)]),268					true269				))}270				--271						"-" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Minus, b))}272						"!" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, b))}273						"~" _ b:@ { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::BitNot, b)) }274				--275				a:(@) _ "[" _ s:slice_desc(s) _ "]" {loc_expr_todo!(Expr::Apply(276					el!(Expr::Index(277						el!(Expr::Var("std".into())),278						el!(Expr::Str("slice".into())),279					)),280					ArgsDesc(vec![281						Arg(None, a),282						Arg(None, s.start.unwrap_or_else(||el!(Expr::Literal(LiteralType::Null)))),283						Arg(None, s.end.unwrap_or_else(||el!(Expr::Literal(LiteralType::Null)))),284						Arg(None, s.step.unwrap_or_else(||el!(Expr::Literal(LiteralType::Null)))),285					]),286					true,287				))}288				a:(@) _ "." _ s:$(id()) {loc_expr_todo!(Expr::Index(a, el!(Expr::Str(s.into()))))}289				a:(@) _ "[" _ s:expr(s) _ "]" {loc_expr_todo!(Expr::Index(a, s))}290				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {loc_expr_todo!(Expr::Apply(a, args, ts.is_some()))}291				a:(@) _ "{" _ body:objinside(s) _ "}" {loc_expr_todo!(Expr::ObjExtend(a, body))}292				--293				e:expr_basic(s) {e}294				"(" _ e:expr(s) _ ")" {loc_expr_todo!(Expr::Parened(e))}295			} end:position!() {296				let LocExpr(e, _) = a;297				LocExpr(e, if s.loc_data {298					Some(ExprLocation(s.file_name.clone(), start, end))299				} else {300					None301				})302			}303			/ e:expr_basic(s) {e}304305		pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}306	}307}308309pub type ParseError = peg::error::ParseError<peg::str::LineCol>;310pub fn parse(str: &str, settings: &ParserSettings) -> Result<LocExpr, ParseError> {311	jsonnet_parser::jsonnet(str, settings)312}313314#[macro_export]315macro_rules! el {316	($expr:expr) => {317		LocExpr(std::rc::Rc::new($expr), None)318	};319}320321#[cfg(test)]322pub mod tests {323	use super::{expr::*, parse};324	use crate::ParserSettings;325	use std::path::PathBuf;326	use std::rc::Rc;327328	macro_rules! parse {329		($s:expr) => {330			parse(331				$s,332				&ParserSettings {333					loc_data: false,334					file_name: Rc::new(PathBuf::from("/test.jsonnet")),335					},336				)337			.unwrap()338		};339	}340341	mod expressions {342		use super::*;343344		pub fn basic_math() -> LocExpr {345			el!(Expr::BinaryOp(346				el!(Expr::Num(2.0)),347				BinaryOpType::Add,348				el!(Expr::BinaryOp(349					el!(Expr::Num(2.0)),350					BinaryOpType::Mul,351					el!(Expr::Num(2.0)),352				)),353			))354		}355	}356357	#[test]358	fn multiline_string() {359		assert_eq!(360			parse!("|||\n    Hello world!\n     a\n|||"),361			el!(Expr::Str("Hello world!\n a\n".into())),362		)363	}364365	#[test]366	fn slice() {367		parse!("a[1:]");368		parse!("a[1::]");369		parse!("a[:1:]");370		parse!("a[::1]");371		parse!("str[:len - 1]");372	}373374	#[test]375	fn string_escaping() {376		assert_eq!(377			parse!(r#""Hello, \"world\"!""#),378			el!(Expr::Str(r#"Hello, "world"!"#.into())),379		);380		assert_eq!(381			parse!(r#"'Hello \'world\'!'"#),382			el!(Expr::Str("Hello 'world'!".into())),383		);384		assert_eq!(parse!(r#"'\\\\'"#), el!(Expr::Str("\\\\".into())),);385	}386387	#[test]388	fn string_unescaping() {389		assert_eq!(390			parse!(r#""Hello\nWorld""#),391			el!(Expr::Str("Hello\nWorld".into())),392		);393	}394395	#[test]396	fn string_verbantim() {397		assert_eq!(398			parse!(r#"@"Hello\n""World""""#),399			el!(Expr::Str("Hello\\n\"World\"".into())),400		);401	}402403	#[test]404	fn imports() {405		assert_eq!(406			parse!("import \"hello\""),407			el!(Expr::Import(PathBuf::from("hello"))),408		);409		assert_eq!(410			parse!("importstr \"garnish.txt\""),411			el!(Expr::ImportStr(PathBuf::from("garnish.txt")))412		);413	}414415	#[test]416	fn empty_object() {417		assert_eq!(parse!("{}"), el!(Expr::Obj(ObjBody::MemberList(vec![]))));418	}419420	#[test]421	fn basic_math() {422		assert_eq!(423			parse!("2+2*2"),424			el!(Expr::BinaryOp(425				el!(Expr::Num(2.0)),426				BinaryOpType::Add,427				el!(Expr::BinaryOp(428					el!(Expr::Num(2.0)),429					BinaryOpType::Mul,430					el!(Expr::Num(2.0))431				))432			))433		);434	}435436	#[test]437	fn basic_math_with_indents() {438		assert_eq!(parse!("2	+ 	  2	  *	2   	"), expressions::basic_math());439	}440441	#[test]442	fn basic_math_parened() {443		assert_eq!(444			parse!("2+(2+2*2)"),445			el!(Expr::BinaryOp(446				el!(Expr::Num(2.0)),447				BinaryOpType::Add,448				el!(Expr::Parened(expressions::basic_math())),449			))450		);451	}452453	/// Comments should not affect parsing454	#[test]455	fn comments() {456		assert_eq!(457			parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),458			el!(Expr::BinaryOp(459				el!(Expr::Num(2.0)),460				BinaryOpType::Add,461				el!(Expr::BinaryOp(462					el!(Expr::Num(3.0)),463					BinaryOpType::Mul,464					el!(Expr::Num(4.0))465				))466			))467		);468	}469470	/// Comments should be able to be escaped471	#[test]472	fn comment_escaping() {473		assert_eq!(474			parse!("2/*\\*/+*/ - 22"),475			el!(Expr::BinaryOp(476				el!(Expr::Num(2.0)),477				BinaryOpType::Sub,478				el!(Expr::Num(22.0))479			))480		);481	}482483	#[test]484	fn suffix() {485		// assert_eq!(parse!("std.test"), el!(Expr::Num(2.2)));486		// assert_eq!(parse!("std(2)"), el!(Expr::Num(2.2)));487		// assert_eq!(parse!("std.test(2)"), el!(Expr::Num(2.2)));488		// assert_eq!(parse!("a[b]"), el!(Expr::Num(2.2)))489	}490491	#[test]492	fn array_comp() {493		use Expr::*;494		assert_eq!(495			parse!("[std.deepJoin(x) for x in arr]"),496			el!(ArrComp(497				el!(Apply(498					el!(Index(el!(Var("std".into())), el!(Str("deepJoin".into())))),499					ArgsDesc(vec![Arg(None, el!(Var("x".into())))]),500					false,501				)),502				vec![CompSpec::ForSpec(ForSpecData(503					"x".into(),504					el!(Var("arr".into()))505				))]506			)),507		)508	}509510	#[test]511	fn reserved() {512		use Expr::*;513		assert_eq!(parse!("null"), el!(Literal(LiteralType::Null)));514		assert_eq!(parse!("nulla"), el!(Var("nulla".into())));515	}516517	#[test]518	fn multiple_args_buf() {519		parse!("a(b, null_fields)");520	}521522	#[test]523	fn infix_precedence() {524		use Expr::*;525		assert_eq!(526			parse!("!a && !b"),527			el!(BinaryOp(528				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into())))),529				BinaryOpType::And,530				el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()))))531			))532		);533	}534535	#[test]536	fn infix_precedence_division() {537		use Expr::*;538		assert_eq!(539			parse!("!a / !b"),540			el!(BinaryOp(541				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into())))),542				BinaryOpType::Div,543				el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()))))544			))545		);546	}547548	#[test]549	fn double_negation() {550		use Expr::*;551		assert_eq!(552			parse!("!!a"),553			el!(UnaryOp(554				UnaryOpType::Not,555				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()))))556			))557		)558	}559560	#[test]561	fn array_test_error() {562		parse!("[a for a in b if c for e in f]");563		//                    ^^^^ failed code564	}565566	#[test]567	fn can_parse_stdlib() {568		parse!(jsonnet_stdlib::STDLIB_STR);569	}570571	use test::Bencher;572573	// From source code574	#[bench]575	fn bench_parse_peg(b: &mut Bencher) {576		b.iter(|| parse!(jsonnet_stdlib::STDLIB_STR))577	}578579	// From serialized blob580	#[bench]581	fn bench_parse_serde_bincode(b: &mut Bencher) {582		let serialized = bincode::serialize(&parse!(jsonnet_stdlib::STDLIB_STR)).unwrap();583		b.iter(|| bincode::deserialize::<LocExpr>(&serialized))584	}585}