git.delta.rocks / jrsonnet / refs/commits / 0f5ef44c0f70

difftreelog

feat experimental null-coaelse operator

Yaroslav Bolyukin2023-07-26parent: #3302e38.patch.diff
in: master

6 files changed

modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -24,6 +24,8 @@
 exp-object-iteration = []
 # Bigint type
 exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]
+# obj?.field, obj?.['field']
+exp-null-coaelse = ["jrsonnet-parser/exp-null-coaelse"]
 
 # Improves performance, and implements some useful things using nightly-only features
 nightly = ["hashbrown/nightly"]
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -446,7 +446,12 @@
 			|| format!("variable <{name}> access"),
 			|| ctx.binding(name.clone())?.evaluate(),
 		)?,
-		Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => {
+		Index {
+			indexable: LocExpr(v, _),
+			index,
+			#[cfg(feature = "exp-null-coaelse")]
+			null_coaelse,
+		} if matches!(&**v, Expr::Literal(LiteralType::Super)) => {
 			let name = evaluate(ctx.clone(), index)?;
 			let Val::Str(name) = name else {
 				throw!(ValueIndexMustBeTypeGot(
@@ -455,17 +460,37 @@
 					name.value_type(),
 				))
 			};
-			ctx.super_obj()
-				.expect("no super found")
-				.get_for(name.into_flat(), ctx.this().expect("no this found").clone())?
-				.expect("value not found")
+			let Some(super_obj) = ctx.super_obj() else {
+				throw!(NoSuperFound)
+			};
+			let this = ctx
+				.this()
+				.expect("no this found, while super present, should not happen");
+			let key = name.into_flat();
+			match super_obj.get_for(key.clone(), this.clone())? {
+				Some(v) => v,
+				#[cfg(feature = "exp-null-coaelse")]
+				None if *null_coaelse => Val::Null,
+				None => {
+					let suggestions = suggest_object_fields(super_obj, key.clone());
+
+					throw!(NoSuchField(key, suggestions))
+				}
+			}
 		}
-		Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {
+		Index {
+			indexable,
+			index,
+			#[cfg(feature = "exp-null-coaelse")]
+			null_coaelse,
+		} => match (evaluate(ctx.clone(), indexable)?, evaluate(ctx, index)?) {
 			(Val::Obj(v), Val::Str(key)) => State::push(
 				CallLocation::new(loc),
 				|| format!("field <{key}> access"),
 				|| match v.get(key.clone().into_flat()) {
 					Ok(Some(v)) => Ok(v),
+					#[cfg(feature = "exp-null-coaelse")]
+					Ok(None) if *null_coaelse => Ok(Val::Null),
 					Ok(None) => {
 						let suggestions = suggest_object_fields(&v, key.clone().into_flat());
 
@@ -514,6 +539,8 @@
 				ValType::Num,
 				n.value_type(),
 			)),
+			#[cfg(feature = "exp-null-coaelse")]
+			(Val::Null, _) if *null_coaelse => Val::Null,
 
 			(v, _) => throw!(CantIndexInto(v.value_type())),
 		},
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -88,6 +88,10 @@
 	Ok(match (evaluate(ctx.clone(), a)?, op, b) {
 		(Bool(true), Or, _o) => Val::Bool(true),
 		(Bool(false), And, _o) => Val::Bool(false),
+		#[cfg(feature = "exp-null-coaelse")]
+		(Null, NullCoaelse, eb) => evaluate(ctx, eb)?,
+		#[cfg(feature = "exp-null-coaelse")]
+		(a, NullCoaelse, _o) => a,
 		(a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(ctx, eb)?)?,
 	})
 }
modifiedcrates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth
10[features]10[features]
11default = []11default = []
12exp-destruct = []12exp-destruct = []
13exp-null-coaelse = []
13# Implement serialization of AST using structdump14# Implement serialization of AST using structdump
14#15#
15# Structdump generates code, which exactly replicated passed AST16# Structdump generates code, which exactly replicated passed AST
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -122,6 +122,8 @@
 
 	And,
 	Or,
+	#[cfg(feature = "exp-null-coaelse")]
+	NullCoaelse,
 
 	// Equialent to std.objectHasEx(a, b, true)
 	In,
@@ -153,6 +155,8 @@
 				And => "&&",
 				Or => "||",
 				In => "in",
+				#[cfg(feature = "exp-null-coaelse")]
+				NullCoaelse => "??",
 			}
 		)
 	}
@@ -399,8 +403,13 @@
 	ErrorStmt(LocExpr),
 	/// a(b, c)
 	Apply(LocExpr, ArgsDesc, bool),
-	/// a[b]
-	Index(LocExpr, LocExpr),
+	/// a[b], a.b, a?.b
+	Index {
+		indexable: LocExpr,
+		index: LocExpr,
+		#[cfg(feature = "exp-null-coaelse")]
+		null_coaelse: bool,
+	},
 	/// function(x) x
 	Function(ParamsDesc, LocExpr),
 	/// if true == false then 1 else 2
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -288,7 +288,11 @@
 		rule unaryop(x: rule<()>) -> ()
 			= quiet!{ x() } / expected!("<unary op>")
 
-
+		rule ensure_null_coaelse()
+			= "" {?
+				#[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled");
+				#[cfg(feature = "exp-null-coaelse")] Ok(())
+			}
 		use BinaryOpType::*;
 		use UnaryOpType::*;
 		rule expr(s: &ParserSettings) -> LocExpr
@@ -296,6 +300,10 @@
 				start:position!() v:@ end:position!() { LocExpr(Rc::new(v), ExprLocation(s.source.clone(), start as u32, end as u32)) }
 				--
 				a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}
+				a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ {
+					#[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b);
+					unreachable!("ensure_null_coaelse will fail if feature is not enabled")
+				}
 				--
 				a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}
 				--
@@ -329,8 +337,16 @@
 						unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}
 				--
 				a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}
-				a:(@) _ "." _ a:position!() e:id_loc(s) b:position!() {Expr::Index(a, e)}
-				a:(@) _ "[" _ e:expr(s) _ "]" {Expr::Index(a, e)}
+				indexable:(@) _ null_coaelse:("?" _ ensure_null_coaelse())? "."  _ index:id_loc(s) {Expr::Index{
+					indexable, index,
+					#[cfg(feature = "exp-null-coaelse")]
+					null_coaelse: null_coaelse.is_some(),
+				}}
+				indexable:(@) _ null_coaelse:("?" _ "." _ ensure_null_coaelse())? "[" _ index:expr(s) _ "]" {Expr::Index{
+					indexable, index,
+					#[cfg(feature = "exp-null-coaelse")]
+					null_coaelse: null_coaelse.is_some(),
+				}}
 				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}
 				a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}
 				--