Refactor to allow more optimized escaping
This commit is contained in:
parent
19559e5c09
commit
faabe73cb2
|
@ -1,21 +1,47 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use array_vec::ArrayStr;
|
||||
|
||||
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.
|
||||
pub trait Escape {
|
||||
pub trait Escape: const EscapeMeta {
|
||||
/// The type of an escaped character.
|
||||
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,
|
||||
/// returns `None`.
|
||||
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.
|
||||
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<Self::Escaped> {
|
||||
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_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<Self::Escaped> {
|
||||
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::<EscapeHtml>::BOOLS);
|
||||
assert!(escape::CommonIdents::<EscapeHtml>::UINTS);
|
||||
assert!(escape::CommonIdents::<EscapeHtml>::INTS);
|
||||
assert!(escape::CommonIdents::<EscapeHtml>::FLOATS);
|
||||
}
|
||||
|
||||
fn escape(feed: &str) -> String {
|
||||
let mut s = String::new();
|
||||
EscapeHtml.escape_to_string(&mut s, feed);
|
||||
|
|
|
@ -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)]
|
||||
|
@ -42,7 +44,7 @@
|
|||
mod utils;
|
||||
|
||||
mod buffer;
|
||||
mod escape;
|
||||
pub mod escape;
|
||||
pub mod filter;
|
||||
mod html_escape;
|
||||
mod render;
|
||||
|
|
|
@ -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::<E>::BOOLS {
|
||||
self.render(b)
|
||||
} else {
|
||||
if *self {
|
||||
|
@ -341,7 +343,7 @@ macro_rules! render_int {
|
|||
|
||||
#[inline]
|
||||
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
|
||||
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::<E>::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::<E>::FLOATS {
|
||||
self.render(b)
|
||||
} else {
|
||||
default_render_escaped(self, b, e)
|
||||
|
|
Loading…
Reference in New Issue