git.delta.rocks / jrsonnet / refs/commits / 4aecc221ffa5

difftreelog

feat(evaluator) ArrComp support

Лач2020-06-03parent: #56fe309.patch.diff
in: master

7 files changed

modifiedcrates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/ctx.rs
+++ b/crates/jsonnet-evaluator/src/ctx.rs
@@ -1,4 +1,7 @@
-use crate::{future_wrapper, rc_fn_helper, LazyBinding, LazyVal, ObjValue};
+use crate::{
+	future_wrapper, lazy_binding, lazy_val, rc_fn_helper, LazyBinding, LazyVal, ObjValue, Val,
+};
+use closure::closure;
 use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
 
 rc_fn_helper!(
@@ -62,6 +65,17 @@
 		ctx.unwrap()
 	}
 
+	pub fn with_var(&self, name: String, value: Val) -> Context {
+		let mut new_bindings: HashMap<_, LazyBinding> = HashMap::new();
+		new_bindings.insert(
+			name,
+			lazy_binding!(
+				closure!(clone value, |_t, _s|lazy_val!(closure!(clone value, ||value.clone())))
+			),
+		);
+		self.extend(new_bindings, None, None, None)
+	}
+
 	pub fn extend(
 		&self,
 		new_bindings: HashMap<String, LazyBinding>,
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -5,8 +5,9 @@
 };
 use closure::closure;
 use jsonnet_parser::{
-	ArgsDesc, BinaryOpType, BindSpec, Expr, FieldMember, LiteralType, LocExpr, Member, ObjBody,
-	ParamsDesc, UnaryOpType, Visibility,
+	el, Arg, ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember,
+	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,
+	Visibility,
 };
 use std::{
 	collections::{BTreeMap, HashMap},
@@ -94,29 +95,64 @@
 	}
 }
 
-pub fn evaluate_binary_op(a: &Val, op: BinaryOpType, b: &Val) -> Val {
+pub fn evaluate_add_op(a: &Val, b: &Val) -> Val {
+	match (a, b) {
+		(Val::Str(v1), Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
+		(Val::Str(v1), Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),
+		(Val::Num(v1), Val::Str(v2)) => Val::Str(format!("{}{}", v1, v2)),
+		(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::Num(v1), Val::Num(v2)) => Val::Num(v1 + v2),
+		_ => panic!("can't add: {:?} and {:?}", a, b),
+	}
+}
+
+pub fn evaluate_binary_op(
+	context: Context,
+	eval_state: EvaluationState,
+	a: &Val,
+	op: BinaryOpType,
+	b: &Val,
+) -> Val {
 	match (a, op, b) {
-		(Val::Lazy(a), o, b) => evaluate_binary_op(&a.evaluate(), o, b),
-		(a, o, Val::Lazy(b)) => evaluate_binary_op(a, o, &b.evaluate()),
+		(Val::Lazy(a), o, b) => evaluate_binary_op(context, eval_state, &a.evaluate(), o, b),
+		(a, o, Val::Lazy(b)) => evaluate_binary_op(context, eval_state, a, o, &b.evaluate()),
+
+		(a, BinaryOpType::Add, b) => evaluate_add_op(a, b),
 
-		(Val::Str(v1), BinaryOpType::Add, Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
 		(Val::Str(v1), BinaryOpType::Ne, Val::Str(v2)) => bool_val(v1 != v2),
 
-		(Val::Str(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),
 		(Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize)),
+		(Val::Str(format), BinaryOpType::Mod, args) => evaluate(
+			context
+				.with_var("__tmp__format__".to_owned(), Val::Str(format.to_owned()))
+				.with_var(
+					"__tmp__args__".to_owned(),
+					match args {
+						Val::Arr(v) => Val::Arr(v.clone()),
+						v => Val::Arr(vec![v.clone()]),
+					},
+				),
+			eval_state,
+			&el!(Expr::Apply(
+				el!(Expr::Index(
+					el!(Expr::Var("std".to_owned())),
+					el!(Expr::Str("format".to_owned()))
+				)),
+				ArgsDesc(vec![
+					Arg(None, el!(Expr::Var("__tmp__format__".to_owned()))),
+					Arg(None, el!(Expr::Var("__tmp__args__".to_owned())))
+				])
+			)),
+		),
 
 		(Val::Bool(a), BinaryOpType::And, Val::Bool(b)) => Val::Bool(*a && *b),
 		(Val::Bool(a), BinaryOpType::Or, Val::Bool(b)) => Val::Bool(*a || *b),
 
-		(Val::Obj(v1), BinaryOpType::Add, Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
-
-		(Val::Arr(a), BinaryOpType::Add, Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),
-
 		(Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Num(v1 * v2),
 		(Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => Val::Num(v1 / v2),
 		(Val::Num(v1), BinaryOpType::Mod, Val::Num(v2)) => Val::Num(v1 % v2),
 
-		(Val::Num(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Num(v1 + v2),
 		(Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2),
 
 		(Val::Num(v1), BinaryOpType::Lhs, Val::Num(v2)) => {
@@ -152,6 +188,42 @@
 future_wrapper!(HashMap<String, LazyBinding>, FutureNewBindings);
 future_wrapper!(ObjValue, FutureObjValue);
 
+pub fn evaluate_comp(
+	context: Context,
+	eval_state: EvaluationState,
+	value: &LocExpr,
+	specs: &[CompSpec],
+) -> Option<Vec<Val>> {
+	match specs.get(0) {
+		None => Some(vec![evaluate(context, eval_state, &value)]),
+		Some(CompSpec::IfSpec(IfSpecData(cond))) => {
+			match evaluate(context.clone(), eval_state.clone(), &cond).unwrap_if_lazy() {
+				Val::Bool(false) => None,
+				Val::Bool(true) => evaluate_comp(context, eval_state, value, &specs[1..]),
+				_ => panic!("if expression evaluated to non-boolean value"),
+			}
+		}
+		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {
+			match evaluate(context.clone(), eval_state.clone(), &expr).unwrap_if_lazy() {
+				Val::Arr(list) => {
+					let mut out = Vec::new();
+					for item in list {
+						let item = item.clone();
+						out.push(evaluate_comp(
+							context.with_var(var.clone(), item),
+							eval_state.clone(),
+							value,
+							&specs[1..],
+						));
+					}
+					Some(out.iter().flatten().flatten().cloned().collect())
+				}
+				_ => panic!("for expression evaluated to non-iterable value"),
+			}
+		}
+	}
+}
+
 // TODO: Asserts
 pub fn evaluate_object(context: Context, eval_state: EvaluationState, object: ObjBody) -> ObjValue {
 	match object {
@@ -272,11 +344,18 @@
 			Parened(e) => evaluate(context, eval_state.clone(), e),
 			Str(v) => Val::Str(v.clone()),
 			Num(v) => Val::Num(*v),
-			BinaryOp(v1, o, v2) => evaluate_binary_op(
-				&evaluate(context.clone(), eval_state.clone(), v1),
-				*o,
-				&evaluate(context, eval_state.clone(), v2),
-			),
+			BinaryOp(v1, o, v2) => {
+				let a = evaluate(context.clone(), eval_state.clone(), v1).unwrap_if_lazy();
+				let op = *o;
+				let b = evaluate(context.clone(), eval_state.clone(), v2).unwrap_if_lazy();
+				evaluate_binary_op(
+					context,
+					eval_state,
+					&a,
+					op,
+					&b,
+				)
+			},
 			UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, eval_state, v)),
 			Var(name) => Val::Lazy(context.binding(&name)).unwrap_if_lazy(),
 			Index(value, index) => {
@@ -286,7 +365,7 @@
 				) {
 					(Val::Obj(v), Val::Str(s)) => v
 						.get(&s)
-						.unwrap_or_else(closure!(clone context, || {
+						.unwrap_or_else(closure!(clone context, clone eval_state, || {
 							if let Some(n) = v.get("__intristic_namespace__") {
 								if let Val::Str(n) = n.unwrap_if_lazy() {
 									Val::Intristic(n, s)
@@ -335,12 +414,16 @@
 				}
 				Val::Arr(out)
 			}
+			ArrComp(expr, compspecs) => {
+				Val::Arr(evaluate_comp(context, eval_state, expr, compspecs).unwrap())
+			}
 			Obj(body) => Val::Obj(evaluate_object(context, eval_state, body.clone())),
 			Apply(value, ArgsDesc(args)) => {
 				let value = evaluate(context.clone(), eval_state.clone(), value).unwrap_if_lazy();
 				match value {
 					// TODO: Capture context of application
 					Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
+						// arr/string/function
 						("std", "length") => {
 							assert_eq!(args.len(), 1);
 							let expr = &args.get(0).unwrap().1;
@@ -350,11 +433,13 @@
 								v => panic!("can't get length of {:?}", v),
 							}
 						}
+						// any
 						("std", "type") => {
 							assert_eq!(args.len(), 1);
 							let expr = &args.get(0).unwrap().1;
 							Val::Str(evaluate(context, eval_state, expr).type_of().to_owned())
 						}
+						// length, idx=>any
 						("std", "makeArray") => {
 							assert_eq!(args.len(), 2);
 							if let (Val::Num(v), Val::Func(d)) = (
@@ -371,6 +456,7 @@
 								panic!("bad makeArray call");
 							}
 						}
+						// string
 						("std", "codepoint") => {
 							assert_eq!(args.len(), 1);
 							if let Val::Str(s) = evaluate(context, eval_state, &args[0].1) {
@@ -383,6 +469,19 @@
 								panic!("bad codepoint call");
 							}
 						}
+						// object, includeHidden
+						("std", "objectFieldsEx") => {
+							assert_eq!(args.len(), 2);
+							if let (Val::Obj(body), Val::Bool(_include_hidden)) = (
+								evaluate(context.clone(), eval_state.clone(), &args[0].1),
+								evaluate(context, eval_state, &args[1].1),
+							) {
+								// TODO: handle visibility (_include_hidden)
+								Val::Arr(body.fields().into_iter().map(Val::Str).collect())
+							} else {
+								panic!("bad objectFieldsEx call");
+							}
+						}
 						(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
 					},
 					Val::Func(f) => f.evaluate(
@@ -402,6 +501,17 @@
 				}
 			}
 			Function(params, body) => evaluate_method(context, eval_state, body, params.clone()),
+			AssertExpr(AssertStmt(value, msg), returned) => {
+				if evaluate(context.clone(), eval_state.clone(), &value).try_cast_bool() {
+					evaluate(context, eval_state, returned)
+				}else {
+					if let Some(msg) = msg {
+						panic!("assertion failed ({:?}): {}", value, evaluate(context, eval_state, msg).try_cast_str());
+					} else {
+						panic!("assertion failed ({:?}): no message", value);
+					}
+				}
+			},
 			Error(e) => panic!("error: {}", evaluate(context, eval_state, e)),
 			IfElse {
 				cond,
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -359,7 +359,12 @@
 
 	#[test]
 	fn json() {
-		println!("{:?}", eval_stdlib!(r#"std.manifestJson({a:3, b:4, c:6})"#));
+		println!("{:?}", eval_stdlib!(r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#));
+	}
+
+	#[test]
+	fn test() {
+		assert_json_stdlib!(r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#, "");
 	}
 
 	#[test]
modifiedcrates/jsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/obj.rs
+++ b/crates/jsonnet-evaluator/src/obj.rs
@@ -1,5 +1,5 @@
-use crate::{evaluate_binary_op, Binding, Val};
-use jsonnet_parser::{BinaryOpType, Visibility};
+use crate::{evaluate_add_op, Binding, Val};
+use jsonnet_parser::Visibility;
 use std::{
 	cell::RefCell,
 	collections::{BTreeMap, BTreeSet, HashMap},
@@ -90,9 +90,8 @@
 			(Some(k), Some(s)) => {
 				let our = k.invoke.0(Some(real_this.clone()), self.0.super_obj.clone());
 				if k.add {
-					s.get_raw(key, real_this).map_or(Some(our.clone()), |v| {
-						Some(evaluate_binary_op(&v, BinaryOpType::Add, &our))
-					})
+					s.get_raw(key, real_this)
+						.map_or(Some(our.clone()), |v| Some(evaluate_add_op(&v, &our)))
 				} else {
 					Some(our)
 				}
modifiedcrates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -117,6 +117,18 @@
 	Intristic(String, String),
 }
 impl Val {
+	pub fn try_cast_bool(self) -> bool {
+		match self.unwrap_if_lazy() {
+			Val::Bool(v) => v,
+			v => panic!("expected bool, got {:?}", v),
+		}
+	}
+	pub fn try_cast_str(self) -> String {
+		match self.unwrap_if_lazy() {
+			Val::Str(v) => v,
+			v => panic!("expected bool, got {:?}", v),
+		}
+	}
 	pub fn unwrap_if_lazy(self) -> Self {
 		if let Val::Lazy(v) = self {
 			v.evaluate().unwrap_if_lazy()
modifiedcrates/jsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jsonnet-parser/src/expr.rs
+++ b/crates/jsonnet-parser/src/expr.rs
@@ -165,7 +165,7 @@
 	///    ]
 	///  ],
 	/// ```
-	ArrComp(LocExpr, ForSpecData, Vec<CompSpec>),
+	ArrComp(LocExpr, Vec<CompSpec>),
 
 	/// Object: {a: 2}
 	Obj(ObjBody),
modifiedcrates/jsonnet-parser/src/lib.rsdiffbeforeafterboth
before · crates/jsonnet-parser/src/lib.rs
1#![feature(box_syntax)]23use peg::parser;4use std::rc::Rc;5mod expr;6pub use expr::*;78enum Suffix {9	String(String),10	Slice(SliceDesc),11	Expression(LocExpr),12	Apply(expr::ArgsDesc),13	Extend(expr::ObjBody),14}15struct LocSuffix(Suffix, ExprLocation);1617pub struct ParserSettings {18	pub loc_data: bool,19	pub file_name: String,20}2122parser! {23	grammar jsonnet_parser() for str {24		use peg::ParseLiteral;2526		/// Standard C-like comments27		rule comment() = "//" (!['\n'][_])* "\n" / "/*" ((!("*/")[_][_])/("\\" "*/"))* "*/"28		rule _() = ([' ' | '\n' | '\t'] / comment())*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() -> u32 = 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) = ##parse_string_literal(id) end_of_ident()45		rule l(s: &ParserSettings, x: rule<Expr>) -> LocExpr = start:position!() v:x() end:position!() {loc_expr!(v, s.loc_data, (s.file_name.clone(), start, end))}4647		pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) }48		pub rule params(s: &ParserSettings) -> expr::ParamsDesc49			= params:(param(s) ** comma()) {50				let mut defaults_started = false;51				for param in &params {52					defaults_started = defaults_started || param.1.is_some();53					assert_eq!(defaults_started, param.1.is_some(), "defauld parameters should be used after all positionals");54				}55				expr::ParamsDesc(params)56			}57			/ { expr::ParamsDesc(Vec::new()) }5859		pub rule arg(s: &ParserSettings) -> expr::Arg60			= name:id() _ "=" _ expr:expr(s) {expr::Arg(Some(name), expr)}61			/ expr:expr(s) {expr::Arg(None, expr)}62		pub rule args(s: &ParserSettings) -> expr::ArgsDesc63			= args:arg(s) ** comma() comma()? {64				let mut named_started = false;65				for arg in &args {66					named_started = named_started || arg.0.is_some();67					assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals");68				}69				expr::ArgsDesc(args)70			}71			/ { expr::ArgsDesc(Vec::new()) }7273		pub rule bind(s: &ParserSettings) -> expr::BindSpec74			= name:id() _ "=" _ expr:expr(s) {expr::BindSpec{name, params: None, value: expr}}75			/ name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name, params: Some(params), value: expr}}76		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }77		pub rule string() -> String78			= "\"" str:$(("\\\"" / !['"'][_])*) "\"" {str.to_owned()}79			/ "'" str:$((!['\''][_])*) "'" {str.to_owned()}80		pub rule field_name(s: &ParserSettings) -> expr::FieldName81			= name:id() {expr::FieldName::Fixed(name)}82			/ name:string() {expr::FieldName::Fixed(name)}83			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}84		pub rule visibility() -> expr::Visibility85			= ":::" {expr::Visibility::Unhide}86			/ "::" {expr::Visibility::Hidden}87			/ ":" {expr::Visibility::Normal}88		pub rule field(s: &ParserSettings) -> expr::FieldMember89			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{90				name,91				plus: plus.is_some(),92				params: None,93				visibility,94				value,95			}}96			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{97				name,98				plus: false,99				params: Some(params),100				visibility,101				value,102			}}103		pub rule obj_local(s: &ParserSettings) -> BindSpec104			= keyword("local") _ bind:bind(s) {bind}105		pub rule member(s: &ParserSettings) -> expr::Member106			= bind:obj_local(s) {expr::Member::BindStmt(bind)}107			/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}108			/ field:field(s) {expr::Member::Field(field)}109		pub rule objinside(s: &ParserSettings) -> expr::ObjBody110			= pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ first:forspec(s) rest:(_ rest:compspec(s) {rest})? {111				expr::ObjBody::ObjComp {112					pre_locals,113					key,114					value,115					post_locals,116					first,117					rest: rest.unwrap_or_default(),118				}119			}120			/ members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}121		pub rule ifspec(s: &ParserSettings) -> IfSpecData = keyword("if") _ expr:expr(s) {IfSpecData(expr)}122		pub rule forspec(s: &ParserSettings) -> ForSpecData = keyword("for") _ id:id() _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}123		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec> = s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} )+ {s}124		pub rule local_expr(s: &ParserSettings) -> LocExpr = l(s,<keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }>)125		pub rule string_expr(s: &ParserSettings) -> LocExpr = l(s, <s:string() {Expr::Str(s)}>)126		pub rule obj_expr(s: &ParserSettings) -> LocExpr = l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>)127		pub rule array_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>)128		pub rule array_comp_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {Expr::ArrComp(expr, forspec, others.unwrap_or_default())}>)129		pub rule number_expr(s: &ParserSettings) -> LocExpr = l(s,<n:number() { expr::Expr::Num(n) }>)130		pub rule var_expr(s: &ParserSettings) -> LocExpr = l(s,<n:id() { expr::Expr::Var(n) }>)131		pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr = l(s,<cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{132			cond,133			cond_then,134			cond_else,135		}}>)136137		pub rule literal(s: &ParserSettings) -> LocExpr138			= l(s,<v:(139				keyword("null") {LiteralType::Null}140				/ keyword("true") {LiteralType::True}141				/ keyword("false") {LiteralType::False}142				/ keyword("self") {LiteralType::This}143				/ keyword("$") {LiteralType::Dollar}144				/ keyword("super") {LiteralType::Super}145			) {Expr::Literal(v)}>)146147		pub rule expr_basic(s: &ParserSettings) -> LocExpr148			= literal(s)149150			/ string_expr(s) / number_expr(s)151			/ array_expr(s)152			/ obj_expr(s)153			/ array_expr(s)154			/ array_comp_expr(s)155156			/ var_expr(s)157			/ local_expr(s)158			/ if_then_else_expr(s)159160			/ l(s,<keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}>)161			/ l(s,<assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }>)162163			/ l(s,<keyword("error") _ expr:expr(s) { Expr::Error(expr) }>)164165		rule expr_basic_with_suffix(s: &ParserSettings) -> LocExpr166			= a:expr_basic(s) suffixes:(_ suffix:l_expr_suffix(s) {suffix})* {167				let mut cur = a;168				for suffix in suffixes {169					let LocSuffix(suffix, location) = suffix;170					cur = LocExpr(Rc::new(match suffix {171						Suffix::String(index) => Expr::Index(cur, loc_expr!(Expr::Str(index), s.loc_data, (s.file_name.clone(), location.1, location.2))),172						Suffix::Slice(desc) => Expr::Slice(cur, desc),173						Suffix::Expression(index) => Expr::Index(cur, index),174						Suffix::Apply(args) => Expr::Apply(cur, args),175						Suffix::Extend(body) => Expr::ObjExtend(cur, body),176					}), if s.loc_data { Some(Rc::new(location)) } else { None })177				}178				cur179			}180181		pub rule slice_desc(s: &ParserSettings) -> SliceDesc182			= start:expr(s)? _ ":" _ pair:(end:expr(s)? _ step:(":" _ e:expr(s) {e})? {(end, step)})? {183				if let Some((end, step)) = pair {184					SliceDesc { start, end, step }185				}else{186					SliceDesc { start, end: None, step: None }187				}188			}189190		rule expr_suffix(s: &ParserSettings) -> Suffix191			= "." _ s:id() { Suffix::String(s) }192			/ "[" _ s:slice_desc(s) _ "]" { Suffix::Slice(s) }193			/ "[" _ s:expr(s) _ "]" { Suffix::Expression(s) }194			/ "(" _ args:args(s) _ ")" (_ keyword("tailstrict"))? { Suffix::Apply(args) }195			/ "{" _ body:objinside(s) _ "}" { Suffix::Extend(body) }196		rule l_expr_suffix(s: &ParserSettings) -> LocSuffix197			= start:position!() suffix:expr_suffix(s) end:position!() {LocSuffix(suffix, ExprLocation(s.file_name.clone(), start, end))}198199		rule expr(s: &ParserSettings) -> LocExpr200			= start:position!() a:precedence! {201				a:(@) _ "||" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Or, b))}202				--203				a:(@) _ "&&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::And, b))}204				--205				a:(@) _ "|" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitOr, b))}206				--207				a:@ _ "^" _ b:(@) {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitXor, b))}208				--209				a:(@) _ "&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitAnd, b))}210				--211				a:(@) _ "==" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Eq, b))}212				a:(@) _ "!=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Ne, b))}213				--214				a:(@) _ "<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lt, b))}215				a:(@) _ ">" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gt, b))}216				a:(@) _ "<=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lte, b))}217				a:(@) _ ">=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gte, b))}218				--219				a:(@) _ "<<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lhs, b))}220				a:(@) _ ">>" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Rhs, b))}221				--222				a:(@) _ "+" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Add, b))}223				a:(@) _ "-" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Sub, b))}224				--225				a:(@) _ "*" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mul, b))}226				a:(@) _ "/" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Div, b))}227				a:(@) _ "%" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mod, b))}228				--229				e:expr_basic_with_suffix(s) {e}230				"-" _ expr:expr_basic_with_suffix(s) { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Minus, expr)) }231				"!" _ expr:expr_basic_with_suffix(s) { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, expr)) }232				"(" _ e:expr(s) _ ")" {loc_expr_todo!(Expr::Parened(e))}233			} end:position!() {234				let LocExpr(e, _) = a;235				LocExpr(e, if s.loc_data {236					Some(Rc::new(ExprLocation(s.file_name.to_owned(), start, end)))237				} else {238					None239				})240			}241			/ e:expr_basic_with_suffix(s) {e}242243		pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}244	}245}246247// TODO: impl FromStr from Expr248pub fn parse(249	str: &str,250	settings: &ParserSettings,251) -> Result<LocExpr, peg::error::ParseError<peg::str::LineCol>> {252	jsonnet_parser::jsonnet(str, settings)253}254255#[cfg(test)]256pub mod tests {257	use super::{expr::*, parse};258	use crate::ParserSettings;259	macro_rules! el {260		($expr:expr) => {261			LocExpr(std::rc::Rc::new($expr), None)262		};263	}264	macro_rules! parse {265		($s:expr) => {266			parse(267				$s,268				&ParserSettings {269					loc_data: false,270					file_name: "test.jsonnet".to_owned(),271					},272				)273			.unwrap()274		};275	}276277	mod expressions {278		use super::*;279280		pub fn basic_math() -> LocExpr {281			el!(Expr::BinaryOp(282				el!(Expr::Num(2.0)),283				BinaryOpType::Add,284				el!(Expr::BinaryOp(285					el!(Expr::Num(2.0)),286					BinaryOpType::Mul,287					el!(Expr::Num(2.0)),288				)),289			))290		}291	}292293	#[test]294	fn empty_object() {295		assert_eq!(parse!("{}"), el!(Expr::Obj(ObjBody::MemberList(vec![]))));296	}297298	#[test]299	fn basic_math() {300		assert_eq!(301			parse!("2+2*2"),302			el!(Expr::BinaryOp(303				el!(Expr::Num(2.0)),304				BinaryOpType::Add,305				el!(Expr::BinaryOp(306					el!(Expr::Num(2.0)),307					BinaryOpType::Mul,308					el!(Expr::Num(2.0))309				))310			))311		);312	}313314	#[test]315	fn basic_math_with_indents() {316		assert_eq!(parse!("2	+ 	  2	  *	2   	"), expressions::basic_math());317	}318319	#[test]320	fn basic_math_parened() {321		assert_eq!(322			parse!("2+(2+2*2)"),323			el!(Expr::BinaryOp(324				el!(Expr::Num(2.0)),325				BinaryOpType::Add,326				el!(Expr::Parened(expressions::basic_math())),327			))328		);329	}330331	/// Comments should not affect parsing332	#[test]333	fn comments() {334		assert_eq!(335			parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),336			el!(Expr::BinaryOp(337				el!(Expr::Num(2.0)),338				BinaryOpType::Add,339				el!(Expr::BinaryOp(340					el!(Expr::Num(3.0)),341					BinaryOpType::Mul,342					el!(Expr::Num(4.0))343				))344			))345		);346	}347348	/// Comments should be able to be escaped349	#[test]350	fn comment_escaping() {351		assert_eq!(352			parse!("2/*\\*/+*/ - 22"),353			el!(Expr::BinaryOp(354				el!(Expr::Num(2.0)),355				BinaryOpType::Sub,356				el!(Expr::Num(22.0))357			))358		);359	}360361	#[test]362	fn suffix_comparsion() {363		use Expr::*;364		assert_eq!(365			parse!("std.type(a) == \"string\""),366			el!(BinaryOp(367				el!(Apply(368					el!(Index(369						el!(Var("std".to_owned())),370						el!(Str("type".to_owned()))371					)),372					ArgsDesc(vec![Arg(None, el!(Var("a".to_owned())))])373				)),374				BinaryOpType::Eq,375				el!(Str("string".to_owned()))376			))377		);378	}379380	#[test]381	fn array_comp() {382		use Expr::*;383		assert_eq!(384			parse!("[std.deepJoin(x) for x in arr]"),385			el!(ArrComp(386				el!(Apply(387					el!(Index(388						el!(Var("std".to_owned())),389						el!(Str("deepJoin".to_owned()))390					)),391					ArgsDesc(vec![Arg(None, el!(Var("x".to_owned())))])392				)),393				ForSpecData("x".to_owned(), el!(Var("arr".to_owned()))),394				vec![]395			)),396		)397	}398399	#[test]400	fn array_comp_with_ifs() {401		use Expr::*;402		assert_eq!(403			parse!("[k for k in std.objectFields(patch) if patch[k] == null]"),404			el!(ArrComp(405				el!(Var("k".to_owned())),406				ForSpecData(407					"k".to_owned(),408					el!(Apply(409						el!(Index(410							el!(Var("std".to_owned())),411							el!(Str("objectFields".to_owned()))412						)),413						ArgsDesc(vec![Arg(None, el!(Var("patch".to_owned())))])414					))415				),416				vec![CompSpec::IfSpec(IfSpecData(el!(BinaryOp(417					el!(Index(418						el!(Var("patch".to_owned())),419						el!(Var("k".to_owned()))420					)),421					BinaryOpType::Eq,422					el!(Literal(LiteralType::Null))423				))))]424			))425		);426	}427428	#[test]429	fn reserved() {430		use Expr::*;431		assert_eq!(parse!("null"), el!(Literal(LiteralType::Null)));432		assert_eq!(parse!("nulla"), el!(Var("nulla".to_owned())));433	}434435	#[test]436	fn multiple_args_buf() {437		parse!("a(b, null_fields)");438	}439440	#[test]441	fn infix_precedence() {442		use Expr::*;443		assert_eq!(444			parse!("!a && !b"),445			el!(BinaryOp(446				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".to_owned())))),447				BinaryOpType::And,448				el!(UnaryOp(UnaryOpType::Not, el!(Var("b".to_owned()))))449			))450		);451	}452453	#[test]454	fn can_parse_stdlib() {455		parse!(jsonnet_stdlib::STDLIB_STR);456	}457}
after · crates/jsonnet-parser/src/lib.rs
1#![feature(box_syntax)]23use peg::parser;4use std::rc::Rc;5mod expr;6pub use expr::*;78enum Suffix {9	String(String),10	Slice(SliceDesc),11	Expression(LocExpr),12	Apply(expr::ArgsDesc),13	Extend(expr::ObjBody),14}15struct LocSuffix(Suffix, ExprLocation);1617pub struct ParserSettings {18	pub loc_data: bool,19	pub file_name: String,20}2122parser! {23	grammar jsonnet_parser() for str {24		use peg::ParseLiteral;2526		/// Standard C-like comments27		rule comment() = "//" (!['\n'][_])* "\n" / "/*" ((!("*/")[_][_])/("\\" "*/"))* "*/"28		rule _() = ([' ' | '\n' | '\t'] / comment())*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() -> u32 = 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) = ##parse_string_literal(id) end_of_ident()45		rule l(s: &ParserSettings, x: rule<Expr>) -> LocExpr = start:position!() v:x() end:position!() {loc_expr!(v, s.loc_data, (s.file_name.clone(), start, end))}4647		pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) }48		pub rule params(s: &ParserSettings) -> expr::ParamsDesc49			= params:(param(s) ** comma()) {50				let mut defaults_started = false;51				for param in &params {52					defaults_started = defaults_started || param.1.is_some();53					assert_eq!(defaults_started, param.1.is_some(), "defauld parameters should be used after all positionals");54				}55				expr::ParamsDesc(params)56			}57			/ { expr::ParamsDesc(Vec::new()) }5859		pub rule arg(s: &ParserSettings) -> expr::Arg60			= name:id() _ "=" _ expr:expr(s) {expr::Arg(Some(name), expr)}61			/ expr:expr(s) {expr::Arg(None, expr)}62		pub rule args(s: &ParserSettings) -> expr::ArgsDesc63			= args:arg(s) ** comma() comma()? {64				let mut named_started = false;65				for arg in &args {66					named_started = named_started || arg.0.is_some();67					assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals");68				}69				expr::ArgsDesc(args)70			}71			/ { expr::ArgsDesc(Vec::new()) }7273		pub rule bind(s: &ParserSettings) -> expr::BindSpec74			= name:id() _ "=" _ expr:expr(s) {expr::BindSpec{name, params: None, value: expr}}75			/ name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name, params: Some(params), value: expr}}76		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }77		pub rule string() -> String78			= "\"" str:$(("\\\"" / !['"'][_])*) "\"" {str.to_owned()}79			/ "'" str:$((!['\''][_])*) "'" {str.to_owned()}80		pub rule field_name(s: &ParserSettings) -> expr::FieldName81			= name:id() {expr::FieldName::Fixed(name)}82			/ name:string() {expr::FieldName::Fixed(name)}83			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}84		pub rule visibility() -> expr::Visibility85			= ":::" {expr::Visibility::Unhide}86			/ "::" {expr::Visibility::Hidden}87			/ ":" {expr::Visibility::Normal}88		pub rule field(s: &ParserSettings) -> expr::FieldMember89			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{90				name,91				plus: plus.is_some(),92				params: None,93				visibility,94				value,95			}}96			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{97				name,98				plus: false,99				params: Some(params),100				visibility,101				value,102			}}103		pub rule obj_local(s: &ParserSettings) -> BindSpec104			= keyword("local") _ bind:bind(s) {bind}105		pub rule member(s: &ParserSettings) -> expr::Member106			= bind:obj_local(s) {expr::Member::BindStmt(bind)}107			/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}108			/ field:field(s) {expr::Member::Field(field)}109		pub rule objinside(s: &ParserSettings) -> expr::ObjBody110			= pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ first:forspec(s) rest:(_ rest:compspec(s) {rest})? {111				expr::ObjBody::ObjComp {112					pre_locals,113					key,114					value,115					post_locals,116					first,117					rest: rest.unwrap_or_default(),118				}119			}120			/ members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}121		pub rule ifspec(s: &ParserSettings) -> IfSpecData = keyword("if") _ expr:expr(s) {IfSpecData(expr)}122		pub rule forspec(s: &ParserSettings) -> ForSpecData = keyword("for") _ id:id() _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}123		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec> = s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} )+ {s}124		pub rule local_expr(s: &ParserSettings) -> LocExpr = l(s,<keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }>)125		pub rule string_expr(s: &ParserSettings) -> LocExpr = l(s, <s:string() {Expr::Str(s)}>)126		pub rule obj_expr(s: &ParserSettings) -> LocExpr = l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>)127		pub rule array_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>)128		pub rule array_comp_expr(s: &ParserSettings) -> LocExpr = 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())}>)129		pub rule number_expr(s: &ParserSettings) -> LocExpr = l(s,<n:number() { expr::Expr::Num(n) }>)130		pub rule var_expr(s: &ParserSettings) -> LocExpr = l(s,<n:id() { expr::Expr::Var(n) }>)131		pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr = l(s,<cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{132			cond,133			cond_then,134			cond_else,135		}}>)136137		pub rule literal(s: &ParserSettings) -> LocExpr138			= l(s,<v:(139				keyword("null") {LiteralType::Null}140				/ keyword("true") {LiteralType::True}141				/ keyword("false") {LiteralType::False}142				/ keyword("self") {LiteralType::This}143				/ keyword("$") {LiteralType::Dollar}144				/ keyword("super") {LiteralType::Super}145			) {Expr::Literal(v)}>)146147		pub rule expr_basic(s: &ParserSettings) -> LocExpr148			= literal(s)149150			/ string_expr(s) / number_expr(s)151			/ array_expr(s)152			/ obj_expr(s)153			/ array_expr(s)154			/ array_comp_expr(s)155156			/ var_expr(s)157			/ local_expr(s)158			/ if_then_else_expr(s)159160			/ l(s,<keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}>)161			/ l(s,<assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }>)162163			/ l(s,<keyword("error") _ expr:expr(s) { Expr::Error(expr) }>)164165		rule expr_basic_with_suffix(s: &ParserSettings) -> LocExpr166			= a:expr_basic(s) suffixes:(_ suffix:l_expr_suffix(s) {suffix})* {167				let mut cur = a;168				for suffix in suffixes {169					let LocSuffix(suffix, location) = suffix;170					cur = LocExpr(Rc::new(match suffix {171						Suffix::String(index) => Expr::Index(cur, loc_expr!(Expr::Str(index), s.loc_data, (s.file_name.clone(), location.1, location.2))),172						Suffix::Slice(desc) => Expr::Slice(cur, desc),173						Suffix::Expression(index) => Expr::Index(cur, index),174						Suffix::Apply(args) => Expr::Apply(cur, args),175						Suffix::Extend(body) => Expr::ObjExtend(cur, body),176					}), if s.loc_data { Some(Rc::new(location)) } else { None })177				}178				cur179			}180181		pub rule slice_desc(s: &ParserSettings) -> SliceDesc182			= start:expr(s)? _ ":" _ pair:(end:expr(s)? _ step:(":" _ e:expr(s) {e})? {(end, step)})? {183				if let Some((end, step)) = pair {184					SliceDesc { start, end, step }185				}else{186					SliceDesc { start, end: None, step: None }187				}188			}189190		rule expr_suffix(s: &ParserSettings) -> Suffix191			= "." _ s:id() { Suffix::String(s) }192			/ "[" _ s:slice_desc(s) _ "]" { Suffix::Slice(s) }193			/ "[" _ s:expr(s) _ "]" { Suffix::Expression(s) }194			/ "(" _ args:args(s) _ ")" (_ keyword("tailstrict"))? { Suffix::Apply(args) }195			/ "{" _ body:objinside(s) _ "}" { Suffix::Extend(body) }196		rule l_expr_suffix(s: &ParserSettings) -> LocSuffix197			= start:position!() suffix:expr_suffix(s) end:position!() {LocSuffix(suffix, ExprLocation(s.file_name.clone(), start, end))}198199		rule expr(s: &ParserSettings) -> LocExpr200			= start:position!() a:precedence! {201				a:(@) _ "||" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Or, b))}202				--203				a:(@) _ "&&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::And, b))}204				--205				a:(@) _ "|" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitOr, b))}206				--207				a:@ _ "^" _ b:(@) {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitXor, b))}208				--209				a:(@) _ "&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitAnd, b))}210				--211				a:(@) _ "==" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Eq, b))}212				a:(@) _ "!=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Ne, b))}213				--214				a:(@) _ "<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lt, b))}215				a:(@) _ ">" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gt, b))}216				a:(@) _ "<=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lte, b))}217				a:(@) _ ">=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gte, b))}218				--219				a:(@) _ "<<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lhs, b))}220				a:(@) _ ">>" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Rhs, b))}221				--222				a:(@) _ "+" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Add, b))}223				a:(@) _ "-" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Sub, b))}224				--225				a:(@) _ "*" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mul, b))}226				a:(@) _ "/" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Div, b))}227				a:(@) _ "%" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mod, b))}228				--229				e:expr_basic_with_suffix(s) {e}230				"-" _ expr:expr_basic_with_suffix(s) { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Minus, expr)) }231				"!" _ expr:expr_basic_with_suffix(s) { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, expr)) }232				"(" _ e:expr(s) _ ")" {loc_expr_todo!(Expr::Parened(e))}233			} end:position!() {234				let LocExpr(e, _) = a;235				LocExpr(e, if s.loc_data {236					Some(Rc::new(ExprLocation(s.file_name.to_owned(), start, end)))237				} else {238					None239				})240			}241			/ e:expr_basic_with_suffix(s) {e}242243		pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}244	}245}246247// TODO: impl FromStr from Expr248pub fn parse(249	str: &str,250	settings: &ParserSettings,251) -> Result<LocExpr, peg::error::ParseError<peg::str::LineCol>> {252	jsonnet_parser::jsonnet(str, settings)253}254255#[macro_export]256macro_rules! el {257	($expr:expr) => {258		LocExpr(std::rc::Rc::new($expr), None)259	};260}261262#[cfg(test)]263pub mod tests {264	use super::{expr::*, parse};265	use crate::ParserSettings;266267	macro_rules! parse {268		($s:expr) => {269			parse(270				$s,271				&ParserSettings {272					loc_data: false,273					file_name: "test.jsonnet".to_owned(),274					},275				)276			.unwrap()277		};278	}279280	mod expressions {281		use super::*;282283		pub fn basic_math() -> LocExpr {284			el!(Expr::BinaryOp(285				el!(Expr::Num(2.0)),286				BinaryOpType::Add,287				el!(Expr::BinaryOp(288					el!(Expr::Num(2.0)),289					BinaryOpType::Mul,290					el!(Expr::Num(2.0)),291				)),292			))293		}294	}295296	#[test]297	fn empty_object() {298		assert_eq!(parse!("{}"), el!(Expr::Obj(ObjBody::MemberList(vec![]))));299	}300301	#[test]302	fn basic_math() {303		assert_eq!(304			parse!("2+2*2"),305			el!(Expr::BinaryOp(306				el!(Expr::Num(2.0)),307				BinaryOpType::Add,308				el!(Expr::BinaryOp(309					el!(Expr::Num(2.0)),310					BinaryOpType::Mul,311					el!(Expr::Num(2.0))312				))313			))314		);315	}316317	#[test]318	fn basic_math_with_indents() {319		assert_eq!(parse!("2	+ 	  2	  *	2   	"), expressions::basic_math());320	}321322	#[test]323	fn basic_math_parened() {324		assert_eq!(325			parse!("2+(2+2*2)"),326			el!(Expr::BinaryOp(327				el!(Expr::Num(2.0)),328				BinaryOpType::Add,329				el!(Expr::Parened(expressions::basic_math())),330			))331		);332	}333334	/// Comments should not affect parsing335	#[test]336	fn comments() {337		assert_eq!(338			parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),339			el!(Expr::BinaryOp(340				el!(Expr::Num(2.0)),341				BinaryOpType::Add,342				el!(Expr::BinaryOp(343					el!(Expr::Num(3.0)),344					BinaryOpType::Mul,345					el!(Expr::Num(4.0))346				))347			))348		);349	}350351	/// Comments should be able to be escaped352	#[test]353	fn comment_escaping() {354		assert_eq!(355			parse!("2/*\\*/+*/ - 22"),356			el!(Expr::BinaryOp(357				el!(Expr::Num(2.0)),358				BinaryOpType::Sub,359				el!(Expr::Num(22.0))360			))361		);362	}363364	#[test]365	fn suffix_comparsion() {366		use Expr::*;367		assert_eq!(368			parse!("std.type(a) == \"string\""),369			el!(BinaryOp(370				el!(Apply(371					el!(Index(372						el!(Var("std".to_owned())),373						el!(Str("type".to_owned()))374					)),375					ArgsDesc(vec![Arg(None, el!(Var("a".to_owned())))])376				)),377				BinaryOpType::Eq,378				el!(Str("string".to_owned()))379			))380		);381	}382383	#[test]384	fn array_comp() {385		use Expr::*;386		assert_eq!(387			parse!("[std.deepJoin(x) for x in arr]"),388			el!(ArrComp(389				el!(Apply(390					el!(Index(391						el!(Var("std".to_owned())),392						el!(Str("deepJoin".to_owned()))393					)),394					ArgsDesc(vec![Arg(None, el!(Var("x".to_owned())))])395				)),396				vec![CompSpec::ForSpec(ForSpecData(397					"x".to_owned(),398					el!(Var("arr".to_owned()))399				))]400			)),401		)402	}403404	#[test]405	fn array_comp_with_ifs() {406		use Expr::*;407		assert_eq!(408			parse!("[k for k in std.objectFields(patch) if patch[k] == null]"),409			el!(ArrComp(410				el!(Var("k".to_owned())),411				vec![412					CompSpec::ForSpec(ForSpecData(413						"k".to_owned(),414						el!(Apply(415							el!(Index(416								el!(Var("std".to_owned())),417								el!(Str("objectFields".to_owned()))418							)),419							ArgsDesc(vec![Arg(None, el!(Var("patch".to_owned())))])420						))421					)),422					CompSpec::IfSpec(IfSpecData(el!(BinaryOp(423						el!(Index(424							el!(Var("patch".to_owned())),425							el!(Var("k".to_owned()))426						)),427						BinaryOpType::Eq,428						el!(Literal(LiteralType::Null))429					))))430				]431			))432		);433	}434435	#[test]436	fn reserved() {437		use Expr::*;438		assert_eq!(parse!("null"), el!(Literal(LiteralType::Null)));439		assert_eq!(parse!("nulla"), el!(Var("nulla".to_owned())));440	}441442	#[test]443	fn multiple_args_buf() {444		parse!("a(b, null_fields)");445	}446447	#[test]448	fn infix_precedence() {449		use Expr::*;450		assert_eq!(451			parse!("!a && !b"),452			el!(BinaryOp(453				el!(UnaryOp(UnaryOpType::Not, el!(Var("a".to_owned())))),454				BinaryOpType::And,455				el!(UnaryOp(UnaryOpType::Not, el!(Var("b".to_owned()))))456			))457		);458	}459460	#[test]461	fn can_parse_stdlib() {462		parse!(jsonnet_stdlib::STDLIB_STR);463	}464}