git.delta.rocks / jrsonnet / refs/commits / 218d8ccde140

difftreelog

perf cheaper error formatting where possible

Yaroslav Bolyukin2023-08-13parent: #6d08ea9.patch.diff
in: master

3 files changed

modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -378,13 +378,13 @@
 		return Err($w$(::$i)*$({$($tt)*})?.into())
 	};
 	($l:literal$(, $($tt:tt)*)?) => {
-		return Err($crate::error::ErrorKind::RuntimeError(format!($l$(, $($tt)*)?).into()).into())
+		return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())
 	};
 }
 
 #[macro_export]
 macro_rules! runtime_error {
 	($l:literal$(, $($tt:tt)*)?) => {
-		$crate::error::Error::from($crate::error::ErrorKind::RuntimeError(format!($l$(, $($tt)*)?).into()))
+		$crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))
 	};
 }
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
84pub use import::*;84pub use import::*;
85use jrsonnet_gcmodule::{Cc, Trace};85use jrsonnet_gcmodule::{Cc, Trace};
86pub use jrsonnet_interner::{IBytes, IStr};86pub use jrsonnet_interner::{IBytes, IStr};
87#[doc(hidden)]
88pub use jrsonnet_macros;
87pub use jrsonnet_parser as parser;89pub use jrsonnet_parser as parser;
88use jrsonnet_parser::*;90use jrsonnet_parser::*;
89pub use obj::*;91pub use obj::*;
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -7,7 +7,7 @@
 	punctuated::Punctuated,
 	spanned::Spanned,
 	token::{self, Comma},
-	Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
+	Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
 	PathArguments, Result, ReturnType, Token, Type,
 };
 
@@ -677,3 +677,102 @@
 		};
 	})
 }
+
+struct FormatInput {
+	formatting: LitStr,
+	arguments: Vec<Expr>,
+}
+impl Parse for FormatInput {
+	fn parse(input: ParseStream) -> Result<Self> {
+		let formatting = input.parse()?;
+		let mut arguments = Vec::new();
+
+		while input.peek(Token![,]) {
+			input.parse::<Token![,]>()?;
+			if input.is_empty() {
+				// Trailing comma
+				break;
+			}
+			let expr = input.parse()?;
+			arguments.push(expr);
+		}
+
+		if !input.is_empty() {
+			return Err(syn::Error::new(input.span(), "unexpected trailing input"));
+		}
+
+		Ok(Self {
+			formatting,
+			arguments,
+		})
+	}
+}
+fn is_format_str(i: &str) -> bool {
+	let mut is_plain = true;
+	// -1 = {
+	// +1 = }
+	let mut is_bracket = 0i8;
+	for ele in i.chars() {
+		match ele {
+			'{' if is_bracket == -1 => {
+				is_bracket = 0;
+			}
+			'}' if is_bracket == -1 => {
+				is_plain = false;
+				break;
+			}
+			'}' if is_bracket == 1 => {
+				is_bracket = 0;
+			}
+			'{' if is_bracket == 1 => {
+				is_plain = false;
+				break;
+			}
+			'{' => {
+				is_bracket = -1;
+			}
+			'}' => {
+				is_bracket = 1;
+			}
+			_ if is_bracket != 0 => {
+				is_plain = false;
+				break;
+			}
+			_ => {}
+		}
+	}
+	!is_plain || is_bracket != 0
+}
+impl FormatInput {
+	fn expand(self) -> TokenStream {
+		let format = self.formatting;
+		if is_format_str(&format.value()) {
+			let args = self.arguments;
+			quote! {
+				::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))
+			}
+		} else {
+			if let Some(first) = self.arguments.first() {
+				return syn::Error::new(
+					first.span(),
+					"string has no formatting codes, it should not have the arguments",
+				)
+				.into_compile_error();
+			}
+			quote! {
+				::jrsonnet_evaluator::IStr::from(#format)
+			}
+		}
+	}
+}
+
+/// IStr formatting helper
+///
+/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`
+/// This macro looks for formatting codes in the input string, and uses
+/// `format!()` only when necessary
+#[proc_macro]
+pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+	let input = parse_macro_input!(input as FormatInput);
+	input.expand().into()
+}