Refactor to allow more optimized escaping
This commit is contained in:
parent
19559e5c09
commit
24180e1cc7
|
@ -1,21 +1,47 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use array_vec::ArrayStr;
|
use array_vec::ArrayStr;
|
||||||
|
|
||||||
use super::Buffer;
|
use super::Buffer;
|
||||||
|
|
||||||
|
pub(crate) struct CommonIdents<E: Escape>(PhantomData<E>);
|
||||||
|
|
||||||
|
pub const fn are_all_chars_identity<E: Escape + ~const EscapeMeta>(set: &[char]) -> bool {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < set.len() {
|
||||||
|
if !E::is_identity(set[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Escape> CommonIdents<E> {
|
||||||
|
/// True if `true` and `false` will never need escaping.
|
||||||
|
pub const BOOLS: bool =
|
||||||
|
are_all_chars_identity::<E>(&['t', 'r', 'u', 'e', 'f', 'a', 'l', 's']);
|
||||||
|
/// True if unsigned integers will never need escaping.
|
||||||
|
pub const UINTS: bool =
|
||||||
|
are_all_chars_identity::<E>(&['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::<E>(&['-']);
|
||||||
|
/// True if floats (using [`ryu`]'s formatting) will never need escaping.
|
||||||
|
pub const FLOATS: bool = Self::INTS && are_all_chars_identity::<E>(&['.', '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.
|
/// A scheme for escaping strings.
|
||||||
pub trait Escape {
|
pub trait Escape: const EscapeMeta {
|
||||||
/// The type of an escaped character.
|
/// The type of an escaped character.
|
||||||
type Escaped: AsRef<str>;
|
type Escaped: AsRef<str>;
|
||||||
|
|
||||||
/// 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,
|
/// If the character needs to be escaped, does so and returns it as a string. Otherwise,
|
||||||
/// returns `None`.
|
/// returns `None`.
|
||||||
fn escape(&self, c: char) -> Option<Self::Escaped>;
|
fn escape(&self, c: char) -> Option<Self::Escaped>;
|
||||||
|
@ -56,14 +82,16 @@ pub trait Escape {
|
||||||
/// A scheme for escaping strings for safe insertion into JSON strings.
|
/// A scheme for escaping strings for safe insertion into JSON strings.
|
||||||
pub struct EscapeJsonString;
|
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 {
|
impl Escape for EscapeJsonString {
|
||||||
type Escaped = ArrayStr<4>;
|
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]
|
#[inline]
|
||||||
fn escape(&self, c: char) -> Option<Self::Escaped> {
|
fn escape(&self, c: char) -> Option<Self::Escaped> {
|
||||||
match c {
|
match c {
|
||||||
|
@ -87,3 +115,16 @@ impl Escape for EscapeJsonString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{CommonIdents, EscapeJsonString};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_idents() {
|
||||||
|
assert!(CommonIdents::<EscapeJsonString>::BOOLS);
|
||||||
|
assert!(CommonIdents::<EscapeJsonString>::UINTS);
|
||||||
|
assert!(CommonIdents::<EscapeJsonString>::INTS);
|
||||||
|
assert!(CommonIdents::<EscapeJsonString>::FLOATS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,20 +34,24 @@ static ESCAPE_LUT: [u8; 256] = [
|
||||||
const ESCAPED: [&str; 5] = [""", "&", "'", "<", ">"];
|
const ESCAPED: [&str; 5] = [""", "&", "'", "<", ">"];
|
||||||
const ESCAPED_LEN: usize = 5;
|
const ESCAPED_LEN: usize = 5;
|
||||||
|
|
||||||
|
use crate::escape::EscapeMeta;
|
||||||
|
|
||||||
use super::{Buffer, Escape};
|
use super::{Buffer, Escape};
|
||||||
|
|
||||||
/// A scheme for escaping strings for safe insertion into HTML.
|
/// A scheme for escaping strings for safe insertion into HTML.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct EscapeHtml;
|
pub struct EscapeHtml;
|
||||||
|
|
||||||
|
impl const EscapeMeta for EscapeHtml {
|
||||||
|
#[inline]
|
||||||
|
fn is_identity(c: char) -> bool {
|
||||||
|
!matches!(c, '\"' | '&' | '<' | '>' | '\'')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Escape for EscapeHtml {
|
impl Escape for EscapeHtml {
|
||||||
type Escaped = &'static str;
|
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)]
|
#[inline(always)]
|
||||||
fn escape(&self, c: char) -> Option<Self::Escaped> {
|
fn escape(&self, c: char) -> Option<Self::Escaped> {
|
||||||
match c {
|
match c {
|
||||||
|
@ -128,8 +132,18 @@ fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::escape;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_idents() {
|
||||||
|
assert!(escape::CommonIdents::<EscapeHtml>::BOOLS);
|
||||||
|
assert!(escape::CommonIdents::<EscapeHtml>::UINTS);
|
||||||
|
assert!(escape::CommonIdents::<EscapeHtml>::INTS);
|
||||||
|
assert!(escape::CommonIdents::<EscapeHtml>::FLOATS);
|
||||||
|
}
|
||||||
|
|
||||||
fn escape(feed: &str) -> String {
|
fn escape(feed: &str) -> String {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
EscapeHtml.escape_to_string(&mut s, feed);
|
EscapeHtml.escape_to_string(&mut s, feed);
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
)]
|
)]
|
||||||
#![cfg_attr(sailfish_nightly, feature(core_intrinsics))]
|
#![cfg_attr(sailfish_nightly, feature(core_intrinsics))]
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
#![feature(const_trait_impl)]
|
||||||
|
#![feature(effects)]
|
||||||
#![feature(maybe_uninit_slice)]
|
#![feature(maybe_uninit_slice)]
|
||||||
#![feature(specialization)]
|
#![feature(specialization)]
|
||||||
#![allow(clippy::redundant_closure)]
|
#![allow(clippy::redundant_closure)]
|
||||||
|
|
|
@ -9,6 +9,8 @@ use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
|
use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
|
use crate::escape;
|
||||||
|
|
||||||
use super::{Buffer, Escape, SizeHint};
|
use super::{Buffer, Escape, SizeHint};
|
||||||
|
|
||||||
/// types which can be rendered inside buffer block (`<%- %>`) by reference
|
/// types which can be rendered inside buffer block (`<%- %>`) by reference
|
||||||
|
@ -303,7 +305,7 @@ impl Render for bool {
|
||||||
b: &mut Buffer,
|
b: &mut Buffer,
|
||||||
e: &E,
|
e: &E,
|
||||||
) -> Result<(), RenderError> {
|
) -> Result<(), RenderError> {
|
||||||
if E::IDENT_BOOLS {
|
if escape::CommonIdents::<E>::BOOLS {
|
||||||
self.render(b)
|
self.render(b)
|
||||||
} else {
|
} else {
|
||||||
if *self {
|
if *self {
|
||||||
|
@ -341,7 +343,7 @@ macro_rules! render_int {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
||||||
if E::$ident_tag {
|
if escape::CommonIdents::<E>::$ident_tag {
|
||||||
// push_str without escape
|
// push_str without escape
|
||||||
self.render(b)
|
self.render(b)
|
||||||
} else {
|
} else {
|
||||||
|
@ -353,8 +355,8 @@ macro_rules! render_int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_int!(IDENT_UINTS, u8, u16, u32, u64, u128, usize);
|
render_int!(UINTS, u8, u16, u32, u64, u128, usize);
|
||||||
render_int!(IDENT_INTS, i8, i16, i32, i64, i128, isize);
|
render_int!(INTS, i8, i16, i32, i64, i128, isize);
|
||||||
|
|
||||||
impl Render for f32 {
|
impl Render for f32 {
|
||||||
#[cfg_attr(feature = "perf-inline", inline)]
|
#[cfg_attr(feature = "perf-inline", inline)]
|
||||||
|
@ -384,7 +386,7 @@ impl Render for f32 {
|
||||||
b: &mut Buffer,
|
b: &mut Buffer,
|
||||||
e: &E,
|
e: &E,
|
||||||
) -> Result<(), RenderError> {
|
) -> Result<(), RenderError> {
|
||||||
if E::IDENT_FLOATS {
|
if escape::CommonIdents::<E>::FLOATS {
|
||||||
self.render(b)
|
self.render(b)
|
||||||
} else {
|
} else {
|
||||||
default_render_escaped(self, b, e)
|
default_render_escaped(self, b, e)
|
||||||
|
@ -420,7 +422,7 @@ impl Render for f64 {
|
||||||
b: &mut Buffer,
|
b: &mut Buffer,
|
||||||
e: &E,
|
e: &E,
|
||||||
) -> Result<(), RenderError> {
|
) -> Result<(), RenderError> {
|
||||||
if E::IDENT_FLOATS {
|
if escape::CommonIdents::<E>::FLOATS {
|
||||||
self.render(b)
|
self.render(b)
|
||||||
} else {
|
} else {
|
||||||
default_render_escaped(self, b, e)
|
default_render_escaped(self, b, e)
|
||||||
|
|
Loading…
Reference in New Issue