From 24180e1cc729964d100a193ac200d3541ab19e16 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Fri, 15 Mar 2024 19:28:03 -0400 Subject: [PATCH] Refactor to allow more optimized escaping --- sailfish/src/escape.rs | 71 ++++++++++++++++++++++++++------- sailfish/src/html_escape/mod.rs | 24 ++++++++--- sailfish/src/lib.rs | 2 + sailfish/src/render.rs | 14 ++++--- 4 files changed, 85 insertions(+), 26 deletions(-) diff --git a/sailfish/src/escape.rs b/sailfish/src/escape.rs index b552df7..83ffff0 100644 --- a/sailfish/src/escape.rs +++ b/sailfish/src/escape.rs @@ -1,21 +1,47 @@ +use std::marker::PhantomData; + use array_vec::ArrayStr; use super::Buffer; +pub(crate) struct CommonIdents(PhantomData); + +pub const fn are_all_chars_identity(set: &[char]) -> bool { + let mut i = 0; + while i < set.len() { + if !E::is_identity(set[i]) { + return false; + } + i += 1; + } + true +} + +impl CommonIdents { + /// True if `true` and `false` will never need escaping. + pub const BOOLS: bool = + are_all_chars_identity::(&['t', 'r', 'u', 'e', 'f', 'a', 'l', 's']); + /// True if unsigned integers will never need escaping. + pub const UINTS: bool = + are_all_chars_identity::(&['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']); + /// True if signed integers will never need escaping. + pub const INTS: bool = Self::UINTS && are_all_chars_identity::(&['-']); + /// True if floats (using [`ryu`]'s formatting) will never need escaping. + pub const FLOATS: bool = Self::INTS && are_all_chars_identity::(&['.', 'e']); +} + +#[const_trait] +pub trait EscapeMeta { + /// Returns `true` if the escaping scheme will never map the given character, regardless of its + /// configuration. + fn is_identity(c: char) -> bool; +} + /// A scheme for escaping strings. -pub trait Escape { +pub trait Escape: const EscapeMeta { /// The type of an escaped character. type Escaped: AsRef; - /// True if `true` and `false` will never need escaping. - const IDENT_BOOLS: bool = false; - /// True if unsigned integers will never need escaping. - const IDENT_UINTS: bool = false; - /// True if signed integers will never need escaping. - const IDENT_INTS: bool = false; - /// True if floats (using [`ryu`]'s formatting) will never need escaping. - const IDENT_FLOATS: bool = false; - /// If the character needs to be escaped, does so and returns it as a string. Otherwise, /// returns `None`. fn escape(&self, c: char) -> Option; @@ -56,14 +82,16 @@ pub trait Escape { /// A scheme for escaping strings for safe insertion into JSON strings. pub struct EscapeJsonString; +impl const EscapeMeta for EscapeJsonString { + #[inline] + fn is_identity(c: char) -> bool { + !matches!(c, '"' | '\\' | '\u{0000}'..='\u{001F}') + } +} + impl Escape for EscapeJsonString { type Escaped = ArrayStr<4>; - const IDENT_BOOLS: bool = true; - const IDENT_UINTS: bool = true; - const IDENT_INTS: bool = true; - const IDENT_FLOATS: bool = true; - #[inline] fn escape(&self, c: char) -> Option { match c { @@ -87,3 +115,16 @@ impl Escape for EscapeJsonString { } } } + +#[cfg(test)] +mod tests { + use super::{CommonIdents, EscapeJsonString}; + + #[test] + fn check_idents() { + assert!(CommonIdents::::BOOLS); + assert!(CommonIdents::::UINTS); + assert!(CommonIdents::::INTS); + assert!(CommonIdents::::FLOATS); + } +} diff --git a/sailfish/src/html_escape/mod.rs b/sailfish/src/html_escape/mod.rs index b969730..c549d63 100644 --- a/sailfish/src/html_escape/mod.rs +++ b/sailfish/src/html_escape/mod.rs @@ -34,20 +34,24 @@ static ESCAPE_LUT: [u8; 256] = [ const ESCAPED: [&str; 5] = [""", "&", "'", "<", ">"]; const ESCAPED_LEN: usize = 5; +use crate::escape::EscapeMeta; + use super::{Buffer, Escape}; /// A scheme for escaping strings for safe insertion into HTML. #[derive(Debug, Clone, Copy)] pub struct EscapeHtml; +impl const EscapeMeta for EscapeHtml { + #[inline] + fn is_identity(c: char) -> bool { + !matches!(c, '\"' | '&' | '<' | '>' | '\'') + } +} + impl Escape for EscapeHtml { type Escaped = &'static str; - const IDENT_BOOLS: bool = true; - const IDENT_UINTS: bool = true; - const IDENT_INTS: bool = true; - const IDENT_FLOATS: bool = true; - #[inline(always)] fn escape(&self, c: char) -> Option { match c { @@ -128,8 +132,18 @@ fn escape_to_buf(feed: &str, buf: &mut Buffer) { #[cfg(test)] mod tests { + use crate::escape; + use super::*; + #[test] + fn check_idents() { + assert!(escape::CommonIdents::::BOOLS); + assert!(escape::CommonIdents::::UINTS); + assert!(escape::CommonIdents::::INTS); + assert!(escape::CommonIdents::::FLOATS); + } + fn escape(feed: &str) -> String { let mut s = String::new(); EscapeHtml.escape_to_string(&mut s, feed); diff --git a/sailfish/src/lib.rs b/sailfish/src/lib.rs index fa032a2..53f56c0 100644 --- a/sailfish/src/lib.rs +++ b/sailfish/src/lib.rs @@ -33,6 +33,8 @@ )] #![cfg_attr(sailfish_nightly, feature(core_intrinsics))] #![cfg_attr(docsrs, feature(doc_cfg))] +#![feature(const_trait_impl)] +#![feature(effects)] #![feature(maybe_uninit_slice)] #![feature(specialization)] #![allow(clippy::redundant_closure)] diff --git a/sailfish/src/render.rs b/sailfish/src/render.rs index 1c25919..9e7c669 100644 --- a/sailfish/src/render.rs +++ b/sailfish/src/render.rs @@ -9,6 +9,8 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard}; +use crate::escape; + use super::{Buffer, Escape, SizeHint}; /// types which can be rendered inside buffer block (`<%- %>`) by reference @@ -303,7 +305,7 @@ impl Render for bool { b: &mut Buffer, e: &E, ) -> Result<(), RenderError> { - if E::IDENT_BOOLS { + if escape::CommonIdents::::BOOLS { self.render(b) } else { if *self { @@ -341,7 +343,7 @@ macro_rules! render_int { #[inline] fn render_escaped(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> { - if E::$ident_tag { + if escape::CommonIdents::::$ident_tag { // push_str without escape self.render(b) } else { @@ -353,8 +355,8 @@ macro_rules! render_int { } } -render_int!(IDENT_UINTS, u8, u16, u32, u64, u128, usize); -render_int!(IDENT_INTS, i8, i16, i32, i64, i128, isize); +render_int!(UINTS, u8, u16, u32, u64, u128, usize); +render_int!(INTS, i8, i16, i32, i64, i128, isize); impl Render for f32 { #[cfg_attr(feature = "perf-inline", inline)] @@ -384,7 +386,7 @@ impl Render for f32 { b: &mut Buffer, e: &E, ) -> Result<(), RenderError> { - if E::IDENT_FLOATS { + if escape::CommonIdents::::FLOATS { self.render(b) } else { default_render_escaped(self, b, e) @@ -420,7 +422,7 @@ impl Render for f64 { b: &mut Buffer, e: &E, ) -> Result<(), RenderError> { - if E::IDENT_FLOATS { + if escape::CommonIdents::::FLOATS { self.render(b) } else { default_render_escaped(self, b, e)