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
--- a/crates/jrsonnet-parser/Cargo.toml
+++ b/crates/jrsonnet-parser/Cargo.toml
@@ -10,6 +10,7 @@
 [features]
 default = []
 exp-destruct = []
+exp-null-coaelse = []
 # Implement serialization of AST using structdump
 #
 # Structdump generates code, which exactly replicated passed AST
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
before · crates/jrsonnet-parser/src/expr.rs
1use std::{2	fmt::{self, Debug, Display},3	ops::Deref,4	rc::Rc,5};67use jrsonnet_gcmodule::Trace;8use jrsonnet_interner::IStr;9#[cfg(feature = "serde")]10use serde::{Deserialize, Serialize};11#[cfg(feature = "structdump")]12use structdump::Codegen;1314use crate::source::Source;1516#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]17#[cfg_attr(feature = "structdump", derive(Codegen))]18#[derive(Debug, PartialEq, Trace)]19pub enum FieldName {20	/// {fixed: 2}21	Fixed(IStr),22	/// {["dyn"+"amic"]: 3}23	Dyn(LocExpr),24}2526#[cfg_attr(feature = "structdump", derive(Codegen))]27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]28#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]29pub enum Visibility {30	/// :31	Normal,32	/// ::33	Hidden,34	/// :::35	Unhide,36}3738impl Visibility {39	pub fn is_visible(&self) -> bool {40		matches!(self, Self::Normal | Self::Unhide)41	}42}4344#[cfg_attr(feature = "structdump", derive(Codegen))]45#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]46#[derive(Clone, Debug, PartialEq, Trace)]47pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);4849#[cfg_attr(feature = "structdump", derive(Codegen))]50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]51#[derive(Debug, PartialEq, Trace)]52pub struct FieldMember {53	pub name: FieldName,54	pub plus: bool,55	pub params: Option<ParamsDesc>,56	pub visibility: Visibility,57	pub value: LocExpr,58}5960#[cfg_attr(feature = "structdump", derive(Codegen))]61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]62#[derive(Debug, PartialEq, Trace)]63pub enum Member {64	Field(FieldMember),65	BindStmt(BindSpec),66	AssertStmt(AssertStmt),67}6869#[cfg_attr(feature = "structdump", derive(Codegen))]70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]71#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]72pub enum UnaryOpType {73	Plus,74	Minus,75	BitNot,76	Not,77}7879impl Display for UnaryOpType {80	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {81		use UnaryOpType::*;82		write!(83			f,84			"{}",85			match self {86				Plus => "+",87				Minus => "-",88				BitNot => "~",89				Not => "!",90			}91		)92	}93}9495#[cfg_attr(feature = "structdump", derive(Codegen))]96#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]97#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]98pub enum BinaryOpType {99	Mul,100	Div,101102	/// Implemented as intrinsic, put here for completeness103	Mod,104105	Add,106	Sub,107108	Lhs,109	Rhs,110111	Lt,112	Gt,113	Lte,114	Gte,115116	BitAnd,117	BitOr,118	BitXor,119120	Eq,121	Neq,122123	And,124	Or,125126	// Equialent to std.objectHasEx(a, b, true)127	In,128}129130impl Display for BinaryOpType {131	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {132		use BinaryOpType::*;133		write!(134			f,135			"{}",136			match self {137				Mul => "*",138				Div => "/",139				Mod => "%",140				Add => "+",141				Sub => "-",142				Lhs => "<<",143				Rhs => ">>",144				Lt => "<",145				Gt => ">",146				Lte => "<=",147				Gte => ">=",148				BitAnd => "&",149				BitOr => "|",150				BitXor => "^",151				Eq => "==",152				Neq => "!=",153				And => "&&",154				Or => "||",155				In => "in",156			}157		)158	}159}160161/// name, default value162#[cfg_attr(feature = "structdump", derive(Codegen))]163#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]164#[derive(Debug, PartialEq, Trace)]165pub struct Param(pub Destruct, pub Option<LocExpr>);166167/// Defined function parameters168#[cfg_attr(feature = "structdump", derive(Codegen))]169#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]170#[derive(Debug, Clone, PartialEq, Trace)]171pub struct ParamsDesc(pub Rc<Vec<Param>>);172173impl Deref for ParamsDesc {174	type Target = Vec<Param>;175	fn deref(&self) -> &Self::Target {176		&self.0177	}178}179180#[cfg_attr(feature = "structdump", derive(Codegen))]181#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]182#[derive(Debug, PartialEq, Trace)]183pub struct ArgsDesc {184	pub unnamed: Vec<LocExpr>,185	pub named: Vec<(IStr, LocExpr)>,186}187impl ArgsDesc {188	pub fn new(unnamed: Vec<LocExpr>, named: Vec<(IStr, LocExpr)>) -> Self {189		Self { unnamed, named }190	}191}192193#[cfg_attr(feature = "structdump", derive(Codegen))]194#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]195#[derive(Debug, Clone, PartialEq, Eq, Trace)]196pub enum DestructRest {197	/// ...rest198	Keep(IStr),199	/// ...200	Drop,201}202203#[cfg_attr(feature = "structdump", derive(Codegen))]204#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]205#[derive(Debug, Clone, PartialEq, Trace)]206pub enum Destruct {207	Full(IStr),208	#[cfg(feature = "exp-destruct")]209	Skip,210	#[cfg(feature = "exp-destruct")]211	Array {212		start: Vec<Destruct>,213		rest: Option<DestructRest>,214		end: Vec<Destruct>,215	},216	#[cfg(feature = "exp-destruct")]217	Object {218		fields: Vec<(IStr, Option<Destruct>, Option<LocExpr>)>,219		rest: Option<DestructRest>,220	},221}222impl Destruct {223	/// Name of destructure, used for function parameter names224	pub fn name(&self) -> Option<IStr> {225		match self {226			Self::Full(name) => Some(name.clone()),227			#[cfg(feature = "exp-destruct")]228			_ => None,229		}230	}231	pub fn capacity_hint(&self) -> usize {232		#[cfg(feature = "exp-destruct")]233		fn cap_rest(rest: &Option<DestructRest>) -> usize {234			match rest {235				Some(DestructRest::Keep(_)) => 1,236				Some(DestructRest::Drop) => 0,237				None => 0,238			}239		}240		match self {241			Self::Full(_) => 1,242			#[cfg(feature = "exp-destruct")]243			Self::Skip => 0,244			#[cfg(feature = "exp-destruct")]245			Self::Array { start, rest, end } => {246				start.iter().map(Destruct::capacity_hint).sum::<usize>()247					+ end.iter().map(Destruct::capacity_hint).sum::<usize>()248					+ cap_rest(rest)249			}250			#[cfg(feature = "exp-destruct")]251			Self::Object { fields, rest } => {252				let mut out = 0;253				for (_, into, _) in fields {254					match into {255						Some(v) => out += v.capacity_hint(),256						// Field is destructured to default name257						None => out += 1,258					}259				}260				out + cap_rest(rest)261			}262		}263	}264}265266#[cfg_attr(feature = "structdump", derive(Codegen))]267#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]268#[derive(Debug, Clone, PartialEq, Trace)]269pub enum BindSpec {270	Field {271		into: Destruct,272		value: LocExpr,273	},274	Function {275		name: IStr,276		params: ParamsDesc,277		value: LocExpr,278	},279}280impl BindSpec {281	pub fn capacity_hint(&self) -> usize {282		match self {283			BindSpec::Field { into, .. } => into.capacity_hint(),284			BindSpec::Function { .. } => 1,285		}286	}287}288289#[cfg_attr(feature = "structdump", derive(Codegen))]290#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]291#[derive(Debug, PartialEq, Trace)]292pub struct IfSpecData(pub LocExpr);293294#[cfg_attr(feature = "structdump", derive(Codegen))]295#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]296#[derive(Debug, PartialEq, Trace)]297pub struct ForSpecData(pub Destruct, pub LocExpr);298299#[cfg_attr(feature = "structdump", derive(Codegen))]300#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]301#[derive(Debug, PartialEq, Trace)]302pub enum CompSpec {303	IfSpec(IfSpecData),304	ForSpec(ForSpecData),305}306307#[cfg_attr(feature = "structdump", derive(Codegen))]308#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]309#[derive(Debug, PartialEq, Trace)]310pub struct ObjComp {311	pub pre_locals: Vec<BindSpec>,312	pub field: FieldMember,313	pub post_locals: Vec<BindSpec>,314	pub compspecs: Vec<CompSpec>,315}316317#[cfg_attr(feature = "structdump", derive(Codegen))]318#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]319#[derive(Debug, PartialEq, Trace)]320pub enum ObjBody {321	MemberList(Vec<Member>),322	ObjComp(ObjComp),323}324325#[cfg_attr(feature = "structdump", derive(Codegen))]326#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]327#[derive(Debug, PartialEq, Eq, Clone, Copy, Trace)]328pub enum LiteralType {329	This,330	Super,331	Dollar,332	Null,333	True,334	False,335}336337#[cfg_attr(feature = "structdump", derive(Codegen))]338#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]339#[derive(Debug, PartialEq, Trace)]340pub struct SliceDesc {341	pub start: Option<LocExpr>,342	pub end: Option<LocExpr>,343	pub step: Option<LocExpr>,344}345346/// Syntax base347#[cfg_attr(feature = "structdump", derive(Codegen))]348#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]349#[derive(Debug, PartialEq, Trace)]350pub enum Expr {351	Literal(LiteralType),352353	/// String value: "hello"354	Str(IStr),355	/// Number: 1, 2.0, 2e+20356	Num(f64),357	/// Variable name: test358	Var(IStr),359360	/// Array of expressions: [1, 2, "Hello"]361	Arr(Vec<LocExpr>),362	/// Array comprehension:363	/// ```jsonnet364	///  ingredients: [365	///    { kind: kind, qty: 4 / 3 }366	///    for kind in [367	///      'Honey Syrup',368	///      'Lemon Juice',369	///      'Farmers Gin',370	///    ]371	///  ],372	/// ```373	ArrComp(LocExpr, Vec<CompSpec>),374375	/// Object: {a: 2}376	Obj(ObjBody),377	/// Object extension: var1 {b: 2}378	ObjExtend(LocExpr, ObjBody),379380	/// (obj)381	Parened(LocExpr),382383	/// -2384	UnaryOp(UnaryOpType, LocExpr),385	/// 2 - 2386	BinaryOp(LocExpr, BinaryOpType, LocExpr),387	/// assert 2 == 2 : "Math is broken"388	AssertExpr(AssertStmt, LocExpr),389	/// local a = 2; { b: a }390	LocalExpr(Vec<BindSpec>, LocExpr),391392	/// import "hello"393	Import(LocExpr),394	/// importStr "file.txt"395	ImportStr(LocExpr),396	/// importBin "file.txt"397	ImportBin(LocExpr),398	/// error "I'm broken"399	ErrorStmt(LocExpr),400	/// a(b, c)401	Apply(LocExpr, ArgsDesc, bool),402	/// a[b]403	Index(LocExpr, LocExpr),404	/// function(x) x405	Function(ParamsDesc, LocExpr),406	/// if true == false then 1 else 2407	IfElse {408		cond: IfSpecData,409		cond_then: LocExpr,410		cond_else: Option<LocExpr>,411	},412	Slice(LocExpr, SliceDesc),413}414415/// file, begin offset, end offset416#[cfg_attr(feature = "structdump", derive(Codegen))]417#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]418#[derive(Clone, PartialEq, Eq, Trace)]419#[trace(skip)]420#[repr(C)]421pub struct ExprLocation(pub Source, pub u32, pub u32);422impl ExprLocation {423	pub fn belongs_to(&self, other: &ExprLocation) -> bool {424		other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2425	}426}427428#[cfg(target_pointer_width = "64")]429static_assertions::assert_eq_size!(ExprLocation, [u8; 16]);430431impl Debug for ExprLocation {432	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {433		write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2)434	}435}436437/// Holds AST expression and its location in source file438#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]439#[cfg_attr(feature = "structdump", derive(Codegen))]440#[derive(Clone, PartialEq, Trace)]441pub struct LocExpr(pub Rc<Expr>, pub ExprLocation);442443#[cfg(target_pointer_width = "64")]444static_assertions::assert_eq_size!(LocExpr, [u8; 24]);445446impl Debug for LocExpr {447	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {448		if f.alternate() {449			write!(f, "{:#?}", self.0)?;450		} else {451			write!(f, "{:?}", self.0)?;452		}453		write!(f, " from {:?}", self.1)?;454		Ok(())455	}456}
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)}
 				--