Merge branch 'master' into stable
This commit is contained in:
commit
67f2bc2870
|
@ -661,11 +661,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "integration-tests"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sailfish 0.1.2",
|
||||
"sailfish-macros 0.1.2",
|
||||
"sailfish 0.1.3",
|
||||
"sailfish-macros 0.1.3",
|
||||
"trybuild 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -693,6 +693,11 @@ name = "itoa"
|
|||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "itoap"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
|
@ -1040,15 +1045,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "sailfish"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"itoap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1060,20 +1066,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-examples"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sailfish 0.1.2",
|
||||
"sailfish-macros 0.1.2",
|
||||
"sailfish 0.1.3",
|
||||
"sailfish-macros 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sailfish-macros"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sailfish-compiler 0.1.2",
|
||||
"sailfish-compiler 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1493,6 +1499,7 @@ dependencies = [
|
|||
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||
"checksum ipconfig 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
|
||||
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
|
||||
"checksum itoap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e804a5759b475f44377998918a7e3be9da3767056f5e77751ef7803893db0e9"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
|
|
@ -30,8 +30,8 @@ Dependencies:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
sailfish = "0.1.2"
|
||||
sailfish-macros = "0.1.2"
|
||||
sailfish = "0.1.3"
|
||||
sailfish-macros = "0.1.3"
|
||||
```
|
||||
|
||||
Template file (templates/hello.stpl):
|
||||
|
|
|
@ -207,6 +207,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tera",
|
||||
"v_htmlescape",
|
||||
"yarte",
|
||||
]
|
||||
|
||||
|
@ -918,6 +919,12 @@ version = "0.4.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||
|
||||
[[package]]
|
||||
name = "itoap"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e804a5759b475f44377998918a7e3be9da3767056f5e77751ef7803893db0e9"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.21"
|
||||
|
@ -1795,6 +1802,7 @@ checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
|||
name = "sailfish"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"itoap",
|
||||
"ryu",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "benches"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
authors = ["Dirkjan Ochtman <dirkjan@ochtman.nl>", "Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
build = "src/build.rs"
|
||||
edition = "2018"
|
||||
|
@ -23,6 +23,7 @@ serde_derive = "1"
|
|||
serde_json = "1"
|
||||
serde_yaml = "0.8"
|
||||
tera = { git = "https://github.com/Keats/tera" }
|
||||
v_htmlescape = "0.9.1"
|
||||
yarte = { git = "https://github.com/botika/yarte", features = ["bytes-buf", "fixed"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -42,5 +42,7 @@ $ cargo bench
|
|||
|
||||
## License
|
||||
|
||||
Copyright (c) 2020 Dirkjan Ochtman and Ryohei Machida
|
||||
|
||||
This benchmark code is distributed under the special permission granted by [Dirkjan Ochtman](https://github.com/djc) (See [this issue](https://github.com/djc/template-benchmarks-rs/issues/26)).
|
||||
**You cannot modify or redistribute the source code without an explicit permission**.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::io::Write;
|
||||
use v_htmlescape::escape;
|
||||
|
||||
use criterion;
|
||||
|
||||
|
@ -68,8 +69,8 @@ pub fn teams(b: &mut criterion::Bencher<'_>, _: &usize) {
|
|||
&mut output,
|
||||
"<li class=\"{champion}\">
|
||||
<b>{name}</b>: {score}",
|
||||
champion = champion,
|
||||
name = team.name,
|
||||
champion = escape(champion),
|
||||
name = escape(&team.name),
|
||||
score = team.score
|
||||
)
|
||||
.unwrap();
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
@ -4,8 +4,8 @@ In order to use sailfish templates, you have add two dependencies in your `Cargo
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
sailfish = "0.1.2"
|
||||
sailfish-macros = "0.1.2"
|
||||
sailfish = "0.1.3"
|
||||
sailfish-macros = "0.1.3"
|
||||
```
|
||||
|
||||
`sailfish` crate contains runtime for rendering contents, and `sailfish-macros` serves you derive macros to compile and import the template files.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-examples"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "integration-tests"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors = ["Kogia-sima <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
|
|
@ -181,13 +181,15 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
}
|
||||
}
|
||||
|
||||
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
|
||||
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
|
||||
));
|
||||
|
||||
#[cfg(feature = "config")]
|
||||
let mut config = Config::search_file_and_read(&*manifest_dir)
|
||||
.map_err(|e| syn::Error::new(Span::call_site(), e))?;
|
||||
let mut config = {
|
||||
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect(
|
||||
"Internal error: environmental variable `CARGO_MANIFEST_DIR` is not set.",
|
||||
));
|
||||
|
||||
Config::search_file_and_read(&*manifest_dir)
|
||||
.map_err(|e| syn::Error::new(Span::call_site(), e))?
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "config"))]
|
||||
let mut config = Config::default();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-macros"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
@ -29,6 +29,6 @@ proc-macro2 = "1.0.11"
|
|||
|
||||
[dependencies.sailfish-compiler]
|
||||
path = "../sailfish-compiler"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
default-features = false
|
||||
features = ["procmacro"]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
@ -17,6 +17,7 @@ default = ["perf-inline"]
|
|||
perf-inline = []
|
||||
|
||||
[dependencies]
|
||||
itoap = "0.1.0"
|
||||
ryu = "1.0.4"
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -44,8 +44,8 @@ pub use runtime::{RenderError, RenderResult};
|
|||
pub trait TemplateOnce: Sized {
|
||||
/// Render the template and return the rendering result as `RenderResult`
|
||||
///
|
||||
/// This method never returns `Err`, unless you manually implement `Render` trait
|
||||
/// for custom type and return `Err` inside it.
|
||||
/// This method never returns `Err`, unless you explicitly return RenderError
|
||||
/// inside templates
|
||||
#[inline]
|
||||
fn render_once(self) -> runtime::RenderResult {
|
||||
let mut buf = String::new();
|
||||
|
@ -54,10 +54,13 @@ pub trait TemplateOnce: Sized {
|
|||
}
|
||||
|
||||
/// Render the template and append the result to `buf`.
|
||||
///
|
||||
/// This method never returns `Err`, unless you explicitly return RenderError
|
||||
/// inside templates
|
||||
fn render_once_to_string(self, buf: &mut String) -> Result<(), RenderError>;
|
||||
}
|
||||
|
||||
/// WIP
|
||||
/// Work in Progress
|
||||
pub trait Template {
|
||||
fn render(&self) -> runtime::RenderResult;
|
||||
}
|
||||
|
|
|
@ -4,20 +4,6 @@ use std::mem::{align_of, ManuallyDrop};
|
|||
use std::ops::{Add, AddAssign};
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(sailfish_nightly)]
|
||||
macro_rules! unlikely {
|
||||
($val:expr) => {
|
||||
std::intrinsics::unlikely($val)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(sailfish_nightly))]
|
||||
macro_rules! unlikely {
|
||||
($val:expr) => {
|
||||
$val
|
||||
};
|
||||
}
|
||||
|
||||
/// Buffer for rendered contents
|
||||
///
|
||||
/// This struct is quite simular to `String`, but some methods are
|
||||
|
@ -41,7 +27,7 @@ impl Buffer {
|
|||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn with_capacity(n: usize) -> Buffer {
|
||||
unsafe {
|
||||
if n == 0 {
|
||||
if unlikely!(n == 0) {
|
||||
Self::new()
|
||||
} else {
|
||||
let layout = Layout::from_size_align_unchecked(n, 1);
|
||||
|
@ -103,6 +89,7 @@ impl Buffer {
|
|||
if unlikely!(self.len + size > self.capacity) {
|
||||
self.reserve_internal(size);
|
||||
}
|
||||
debug_assert!(self.len + size <= self.capacity);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -115,6 +102,7 @@ impl Buffer {
|
|||
/// This consumes the `Buffer`, so we do not need to copy its contents.
|
||||
#[inline]
|
||||
pub fn into_string(self) -> String {
|
||||
debug_assert!(self.len <= self.capacity);
|
||||
let buf = ManuallyDrop::new(self);
|
||||
unsafe { String::from_raw_parts(buf.data, buf.len, buf.capacity) }
|
||||
}
|
||||
|
@ -128,6 +116,7 @@ impl Buffer {
|
|||
std::ptr::copy_nonoverlapping(data.as_ptr(), p, size);
|
||||
self.len += size;
|
||||
}
|
||||
debug_assert!(self.len <= self.capacity);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -137,31 +126,35 @@ impl Buffer {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
#[cold]
|
||||
fn reserve_internal(&mut self, size: usize) {
|
||||
unsafe {
|
||||
let new_capacity = std::cmp::max(self.capacity * 2, self.len + size);
|
||||
self.data = self.realloc(new_capacity);
|
||||
debug_assert!(new_capacity > self.capacity);
|
||||
self.data = safe_realloc(self.data, self.capacity, new_capacity);
|
||||
self.capacity = new_capacity;
|
||||
}
|
||||
debug_assert!(!self.data.is_null());
|
||||
debug_assert!(self.len <= self.capacity);
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
unsafe fn safe_realloc(ptr: *mut u8, capacity: usize, new_capacity: usize) -> *mut u8 {
|
||||
assert!(new_capacity <= std::usize::MAX / 2, "capacity is too large");
|
||||
let data = if unlikely!(capacity == 0) {
|
||||
let new_layout = Layout::from_size_align_unchecked(new_capacity, 1);
|
||||
alloc(new_layout)
|
||||
} else {
|
||||
let old_layout = Layout::from_size_align_unchecked(capacity, 1);
|
||||
realloc(ptr, old_layout, new_capacity)
|
||||
};
|
||||
|
||||
if data.is_null() {
|
||||
handle_alloc_error(Layout::from_size_align_unchecked(new_capacity, 1));
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
unsafe fn realloc(&self, cap: usize) -> *mut u8 {
|
||||
let data = if unlikely!(self.capacity == 0) {
|
||||
let new_layout = Layout::from_size_align_unchecked(cap, 1);
|
||||
alloc(new_layout)
|
||||
} else {
|
||||
debug_assert!(cap <= std::usize::MAX / 2, "capacity is too large");
|
||||
let old_layout = Layout::from_size_align_unchecked(self.capacity, 1);
|
||||
realloc(self.data, old_layout, cap)
|
||||
};
|
||||
|
||||
if data.is_null() {
|
||||
handle_alloc_error(Layout::from_size_align_unchecked(cap, 1));
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
impl Clone for Buffer {
|
||||
|
|
|
@ -7,7 +7,6 @@ use std::arch::x86_64::*;
|
|||
use std::slice;
|
||||
|
||||
use super::super::Buffer;
|
||||
use super::{naive, sse2};
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
|
||||
const VECTOR_BYTES: usize = std::mem::size_of::<__m256i>();
|
||||
|
@ -15,15 +14,12 @@ const VECTOR_ALIGN: usize = VECTOR_BYTES - 1;
|
|||
|
||||
#[target_feature(enable = "avx2")]
|
||||
pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
||||
debug_assert!(feed.len() >= 16);
|
||||
|
||||
let len = feed.len();
|
||||
|
||||
if len < 8 {
|
||||
let start_ptr = feed.as_ptr();
|
||||
let end_ptr = start_ptr.add(len);
|
||||
naive::escape(buffer, start_ptr, start_ptr, end_ptr);
|
||||
return;
|
||||
} else if len < VECTOR_BYTES {
|
||||
sse2::escape(feed, buffer);
|
||||
if len < VECTOR_BYTES {
|
||||
escape_small(feed, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -35,11 +31,11 @@ pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
|||
let v_key1 = _mm256_set1_epi8(0x27);
|
||||
let v_key2 = _mm256_set1_epi8(0x3e);
|
||||
|
||||
let maskgen = |x: __m256i| -> i32 {
|
||||
let maskgen = |x: __m256i| -> u32 {
|
||||
_mm256_movemask_epi8(_mm256_or_si256(
|
||||
_mm256_cmpeq_epi8(_mm256_or_si256(x, v_independent1), v_key1),
|
||||
_mm256_cmpeq_epi8(_mm256_or_si256(x, v_independent2), v_key2),
|
||||
))
|
||||
)) as u32
|
||||
};
|
||||
|
||||
let mut ptr = start_ptr;
|
||||
|
@ -97,5 +93,106 @@ pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
|||
next_ptr = next_ptr.add(VECTOR_BYTES);
|
||||
}
|
||||
|
||||
sse2::escape_aligned(buffer, start_ptr, ptr, end_ptr);
|
||||
debug_assert!(next_ptr > end_ptr);
|
||||
|
||||
if ptr < end_ptr {
|
||||
debug_assert!((end_ptr as usize - ptr as usize) < VECTOR_BYTES);
|
||||
let backs = VECTOR_BYTES - (end_ptr as usize - ptr as usize);
|
||||
|
||||
let mut mask =
|
||||
maskgen(_mm256_loadu_si256(ptr.sub(backs) as *const __m256i)) >> backs;
|
||||
while mask != 0 {
|
||||
let trailing_zeros = mask.trailing_zeros() as usize;
|
||||
let ptr2 = ptr.add(trailing_zeros);
|
||||
let c = ESCAPE_LUT[*ptr2 as usize] as usize;
|
||||
if c < ESCAPED_LEN {
|
||||
if start_ptr < ptr2 {
|
||||
let slc = slice::from_raw_parts(
|
||||
start_ptr,
|
||||
ptr2 as usize - start_ptr as usize,
|
||||
);
|
||||
buffer.push_str(std::str::from_utf8_unchecked(slc));
|
||||
}
|
||||
buffer.push_str(*ESCAPED.get_unchecked(c));
|
||||
start_ptr = ptr2.add(1);
|
||||
}
|
||||
mask ^= 1 << trailing_zeros;
|
||||
}
|
||||
}
|
||||
|
||||
if end_ptr > start_ptr {
|
||||
let slc = slice::from_raw_parts(start_ptr, end_ptr as usize - start_ptr as usize);
|
||||
buffer.push_str(std::str::from_utf8_unchecked(slc));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[target_feature(enable = "avx2")]
|
||||
unsafe fn escape_small(feed: &str, buffer: &mut Buffer) {
|
||||
debug_assert!(feed.len() >= 16);
|
||||
debug_assert!(feed.len() < VECTOR_BYTES);
|
||||
|
||||
let len = feed.len();
|
||||
let mut start_ptr = feed.as_ptr();
|
||||
let mut ptr = start_ptr;
|
||||
let end_ptr = start_ptr.add(len);
|
||||
|
||||
let v_independent1 = _mm_set1_epi8(5);
|
||||
let v_independent2 = _mm_set1_epi8(2);
|
||||
let v_key1 = _mm_set1_epi8(0x27);
|
||||
let v_key2 = _mm_set1_epi8(0x3e);
|
||||
|
||||
let maskgen = |x: __m128i| -> u32 {
|
||||
_mm_movemask_epi8(_mm_or_si128(
|
||||
_mm_cmpeq_epi8(_mm_or_si128(x, v_independent1), v_key1),
|
||||
_mm_cmpeq_epi8(_mm_or_si128(x, v_independent2), v_key2),
|
||||
)) as u32
|
||||
};
|
||||
|
||||
let mut mask = maskgen(_mm_loadu_si128(ptr as *const __m128i));
|
||||
while mask != 0 {
|
||||
let trailing_zeros = mask.trailing_zeros() as usize;
|
||||
let ptr2 = ptr.add(trailing_zeros);
|
||||
let c = ESCAPE_LUT[*ptr2 as usize] as usize;
|
||||
if c < ESCAPED_LEN {
|
||||
if start_ptr < ptr2 {
|
||||
let slc =
|
||||
slice::from_raw_parts(start_ptr, ptr2 as usize - start_ptr as usize);
|
||||
buffer.push_str(std::str::from_utf8_unchecked(slc));
|
||||
}
|
||||
buffer.push_str(*ESCAPED.get_unchecked(c));
|
||||
start_ptr = ptr2.add(1);
|
||||
}
|
||||
mask ^= 1 << trailing_zeros;
|
||||
}
|
||||
|
||||
if len != 16 {
|
||||
ptr = ptr.add(16);
|
||||
let read_ptr = end_ptr.sub(16);
|
||||
let backs = 32 - len;
|
||||
let mut mask = maskgen(_mm_loadu_si128(read_ptr as *const __m128i)) >> backs;
|
||||
|
||||
while mask != 0 {
|
||||
let trailing_zeros = mask.trailing_zeros() as usize;
|
||||
let ptr2 = ptr.add(trailing_zeros);
|
||||
let c = ESCAPE_LUT[*ptr2 as usize] as usize;
|
||||
if c < ESCAPED_LEN {
|
||||
if start_ptr < ptr2 {
|
||||
let slc = slice::from_raw_parts(
|
||||
start_ptr,
|
||||
ptr2 as usize - start_ptr as usize,
|
||||
);
|
||||
buffer.push_str(std::str::from_utf8_unchecked(slc));
|
||||
}
|
||||
buffer.push_str(*ESCAPED.get_unchecked(c));
|
||||
start_ptr = ptr2.add(1);
|
||||
}
|
||||
mask ^= 1 << trailing_zeros;
|
||||
}
|
||||
}
|
||||
|
||||
if end_ptr > start_ptr {
|
||||
let slc = slice::from_raw_parts(start_ptr, end_ptr as usize - start_ptr as usize);
|
||||
buffer.push_str(std::str::from_utf8_unchecked(slc));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
|||
return;
|
||||
}
|
||||
|
||||
let ptr = start_ptr;
|
||||
let mut ptr = start_ptr;
|
||||
let aligned_ptr = ptr.add(USIZE_BYTES - (start_ptr as usize & USIZE_ALIGN));
|
||||
debug_assert_eq!(aligned_ptr as usize % USIZE_BYTES, 0);
|
||||
debug_assert!(aligned_ptr <= end_ptr);
|
||||
|
@ -59,15 +59,8 @@ pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
|||
start_ptr = naive::proceed(buffer, start_ptr, ptr, aligned_ptr);
|
||||
}
|
||||
|
||||
escape_aligned(buffer, start_ptr, aligned_ptr, end_ptr);
|
||||
}
|
||||
ptr = aligned_ptr;
|
||||
|
||||
pub unsafe fn escape_aligned(
|
||||
buffer: &mut Buffer,
|
||||
mut start_ptr: *const u8,
|
||||
mut ptr: *const u8,
|
||||
end_ptr: *const u8,
|
||||
) {
|
||||
while ptr.add(USIZE_BYTES) <= end_ptr {
|
||||
debug_assert_eq!((ptr as usize) % USIZE_BYTES, 0);
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
//! HTML escaping
|
||||
//! HTML escaping utilities
|
||||
//!
|
||||
//! By default sailfish replaces the characters `&"'<>` with the equivalent html.
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod avx2;
|
||||
mod fallback;
|
||||
mod naive;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod sse2;
|
||||
|
||||
use std::mem;
|
||||
|
@ -30,16 +32,12 @@ static ESCAPE_LUT: [u8; 256] = [
|
|||
const ESCAPED: [&str; 5] = [""", "&", "'", "<", ">"];
|
||||
const ESCAPED_LEN: usize = 5;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
static FN: AtomicPtr<()> = AtomicPtr::new(escape as FnRaw);
|
||||
|
||||
#[cfg(target_feature = "avx2")]
|
||||
#[inline]
|
||||
fn escape(feed: &str, buf: &mut Buffer) {
|
||||
unsafe { avx2::escape(feed, buf) }
|
||||
}
|
||||
|
||||
#[cfg(not(target_feature = "avx2"))]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn escape(feed: &str, buf: &mut Buffer) {
|
||||
debug_assert!(feed.len() >= 16);
|
||||
let fun = if is_x86_feature_detected!("avx2") {
|
||||
avx2::escape
|
||||
} else if is_x86_feature_detected!("sse2") {
|
||||
|
@ -53,20 +51,37 @@ fn escape(feed: &str, buf: &mut Buffer) {
|
|||
}
|
||||
|
||||
/// Change the default escape function
|
||||
#[deprecated(since = "0.1.2", note = "This function does not anything any more")]
|
||||
#[doc(hidden)]
|
||||
pub fn register_escape_fn(_fun: fn(&str, &mut Buffer)) {}
|
||||
|
||||
pub(crate) fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
unsafe {
|
||||
if feed.len() < 16 {
|
||||
let start_ptr = feed.as_ptr();
|
||||
let end_ptr = start_ptr.add(feed.len());
|
||||
naive::escape(buf, start_ptr, start_ptr, end_ptr);
|
||||
return;
|
||||
}
|
||||
buf.reserve(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.set_len(buf.len() + l);
|
||||
} else {
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
{
|
||||
#[cfg(target_feature = "avx2")]
|
||||
{
|
||||
avx2::escape(feed, buf);
|
||||
}
|
||||
|
||||
let fun = FN.load(Ordering::Relaxed);
|
||||
mem::transmute::<FnRaw, fn(&str, &mut Buffer)>(fun)(feed, buf);
|
||||
#[cfg(not(target_feature = "avx2"))]
|
||||
{
|
||||
let fun = FN.load(Ordering::Relaxed);
|
||||
mem::transmute::<FnRaw, fn(&str, &mut Buffer)>(fun)(feed, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
{
|
||||
fallback::escape(feed, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,31 +164,38 @@ mod tests {
|
|||
let mut buf3 = Buffer::new();
|
||||
|
||||
for len in 0..100 {
|
||||
data.clear();
|
||||
for _ in 0..len {
|
||||
// xorshift
|
||||
state ^= state << 13;
|
||||
state ^= state >> 7;
|
||||
state ^= state << 17;
|
||||
for _ in 0..10 {
|
||||
data.clear();
|
||||
for _ in 0..len {
|
||||
// xorshift
|
||||
state ^= state << 13;
|
||||
state ^= state >> 7;
|
||||
state ^= state << 17;
|
||||
|
||||
let idx = state as usize % ASCII_CHARS.len();
|
||||
data.push(ASCII_CHARS[idx]);
|
||||
let idx = state as usize % ASCII_CHARS.len();
|
||||
data.push(ASCII_CHARS[idx]);
|
||||
}
|
||||
|
||||
let s = unsafe { std::str::from_utf8_unchecked(&*data) };
|
||||
|
||||
buf1.clear();
|
||||
buf2.clear();
|
||||
buf3.clear();
|
||||
|
||||
unsafe {
|
||||
escape_to_buf(s, &mut buf1);
|
||||
fallback::escape(s, &mut buf2);
|
||||
naive::escape(
|
||||
&mut buf3,
|
||||
s.as_ptr(),
|
||||
s.as_ptr(),
|
||||
s.as_ptr().add(s.len()),
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(buf1.as_str(), buf3.as_str());
|
||||
assert_eq!(buf2.as_str(), buf3.as_str());
|
||||
}
|
||||
|
||||
let s = unsafe { std::str::from_utf8_unchecked(&*data) };
|
||||
|
||||
buf1.clear();
|
||||
buf2.clear();
|
||||
buf3.clear();
|
||||
|
||||
unsafe {
|
||||
escape_to_buf(s, &mut buf1);
|
||||
fallback::escape(s, &mut buf2);
|
||||
naive::escape(&mut buf3, s.as_ptr(), s.as_ptr(), s.as_ptr().add(s.len()));
|
||||
}
|
||||
|
||||
assert_eq!(buf1.as_str(), buf3.as_str());
|
||||
assert_eq!(buf2.as_str(), buf3.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use core::slice;
|
||||
|
||||
use super::super::utils::memcpy_16;
|
||||
use super::super::Buffer;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
|
||||
|
@ -12,7 +13,7 @@ pub(super) unsafe fn escape(
|
|||
) {
|
||||
start_ptr = proceed(buffer, start_ptr, ptr, end_ptr);
|
||||
|
||||
if end_ptr > start_ptr {
|
||||
if likely!(end_ptr > start_ptr) {
|
||||
let slc = slice::from_raw_parts(start_ptr, end_ptr as usize - start_ptr as usize);
|
||||
buffer.push_str(std::str::from_utf8_unchecked(slc));
|
||||
}
|
||||
|
@ -29,7 +30,7 @@ pub(super) unsafe fn proceed(
|
|||
debug_assert!(start_ptr <= ptr);
|
||||
let idx = ESCAPE_LUT[*ptr as usize] as usize;
|
||||
debug_assert!(idx <= 9);
|
||||
if idx < ESCAPED_LEN {
|
||||
if unlikely!(idx < ESCAPED_LEN) {
|
||||
if ptr > start_ptr {
|
||||
let slc =
|
||||
slice::from_raw_parts(start_ptr, ptr as usize - start_ptr as usize);
|
||||
|
@ -45,3 +46,41 @@ pub(super) unsafe fn proceed(
|
|||
debug_assert!(start_ptr <= ptr);
|
||||
start_ptr
|
||||
}
|
||||
|
||||
pub(super) unsafe fn escape_small(feed: &str, mut buf: *mut u8) -> usize {
|
||||
let mut start_ptr = feed.as_ptr();
|
||||
let mut ptr = start_ptr;
|
||||
let end_ptr = start_ptr.add(feed.len());
|
||||
let buf_begin = buf;
|
||||
|
||||
while ptr < end_ptr {
|
||||
debug_assert!(start_ptr <= ptr);
|
||||
let idx = *ESCAPE_LUT.get_unchecked(*ptr as usize) as usize;
|
||||
debug_assert!(idx <= 9);
|
||||
if unlikely!(idx < ESCAPED_LEN) {
|
||||
let escaped = ESCAPED.get_unchecked(idx);
|
||||
if ptr > start_ptr {
|
||||
let slc =
|
||||
slice::from_raw_parts(start_ptr, ptr as usize - start_ptr as usize);
|
||||
|
||||
memcpy_16(slc.as_ptr(), buf, slc.len());
|
||||
buf = buf.add(slc.len());
|
||||
}
|
||||
memcpy_16(escaped.as_ptr(), buf, escaped.len());
|
||||
buf = buf.add(escaped.len());
|
||||
start_ptr = ptr.add(1);
|
||||
}
|
||||
ptr = ptr.add(1);
|
||||
}
|
||||
|
||||
debug_assert_eq!(ptr, end_ptr);
|
||||
debug_assert!(start_ptr <= ptr);
|
||||
|
||||
if likely!(end_ptr > start_ptr) {
|
||||
let slc = slice::from_raw_parts(start_ptr, end_ptr as usize - start_ptr as usize);
|
||||
memcpy_16(slc.as_ptr(), buf, slc.len());
|
||||
buf = buf.add(slc.len());
|
||||
}
|
||||
|
||||
buf as usize - buf_begin as usize
|
||||
}
|
||||
|
|
|
@ -7,23 +7,14 @@ use std::arch::x86_64::*;
|
|||
use std::slice;
|
||||
|
||||
use super::super::Buffer;
|
||||
use super::naive;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
|
||||
const VECTOR_BYTES: usize = std::mem::size_of::<__m128i>();
|
||||
const VECTOR_ALIGN: usize = VECTOR_BYTES - 1;
|
||||
|
||||
#[target_feature(enable = "sse2")]
|
||||
#[inline]
|
||||
pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
||||
let len = feed.len();
|
||||
if len < VECTOR_BYTES {
|
||||
let start_ptr = feed.as_ptr();
|
||||
let end_ptr = start_ptr.add(len);
|
||||
naive::escape(buffer, start_ptr, start_ptr, end_ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut start_ptr = feed.as_ptr();
|
||||
let end_ptr = start_ptr.add(len);
|
||||
|
||||
|
@ -32,11 +23,11 @@ pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
|||
let v_key1 = _mm_set1_epi8(0x27);
|
||||
let v_key2 = _mm_set1_epi8(0x3e);
|
||||
|
||||
let maskgen = |x: __m128i| -> i32 {
|
||||
let maskgen = |x: __m128i| -> u32 {
|
||||
_mm_movemask_epi8(_mm_or_si128(
|
||||
_mm_cmpeq_epi8(_mm_or_si128(x, v_independent1), v_key1),
|
||||
_mm_cmpeq_epi8(_mm_or_si128(x, v_independent2), v_key2),
|
||||
))
|
||||
)) as u32
|
||||
};
|
||||
|
||||
let mut ptr = start_ptr;
|
||||
|
@ -68,29 +59,7 @@ pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
|||
}
|
||||
|
||||
ptr = aligned_ptr;
|
||||
escape_aligned(buffer, start_ptr, ptr, end_ptr);
|
||||
}
|
||||
|
||||
#[target_feature(enable = "sse2")]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub unsafe fn escape_aligned(
|
||||
buffer: &mut Buffer,
|
||||
mut start_ptr: *const u8,
|
||||
mut ptr: *const u8,
|
||||
end_ptr: *const u8,
|
||||
) {
|
||||
let mut next_ptr = ptr.add(VECTOR_BYTES);
|
||||
let v_independent1 = _mm_set1_epi8(5);
|
||||
let v_independent2 = _mm_set1_epi8(2);
|
||||
let v_key1 = _mm_set1_epi8(0x27);
|
||||
let v_key2 = _mm_set1_epi8(0x3e);
|
||||
|
||||
let maskgen = |x: __m128i| -> i32 {
|
||||
_mm_movemask_epi8(_mm_or_si128(
|
||||
_mm_cmpeq_epi8(_mm_or_si128(x, v_independent1), v_key1),
|
||||
_mm_cmpeq_epi8(_mm_or_si128(x, v_independent2), v_key2),
|
||||
))
|
||||
};
|
||||
|
||||
while next_ptr <= end_ptr {
|
||||
debug_assert_eq!((ptr as usize) % VECTOR_BYTES, 0);
|
||||
|
@ -117,10 +86,14 @@ pub unsafe fn escape_aligned(
|
|||
next_ptr = next_ptr.add(VECTOR_BYTES);
|
||||
}
|
||||
|
||||
next_ptr = ptr.add(8);
|
||||
if next_ptr <= end_ptr {
|
||||
debug_assert_eq!((ptr as usize) % VECTOR_BYTES, 0);
|
||||
let mut mask = maskgen(_mm_loadl_epi64(ptr as *const __m128i));
|
||||
debug_assert!(next_ptr > end_ptr);
|
||||
|
||||
if ptr < end_ptr {
|
||||
debug_assert!((end_ptr as usize - ptr as usize) < VECTOR_BYTES);
|
||||
let backs = VECTOR_BYTES - (end_ptr as usize - ptr as usize);
|
||||
let read_ptr = ptr.sub(backs);
|
||||
|
||||
let mut mask = maskgen(_mm_loadu_si128(read_ptr as *const __m128i)) >> backs;
|
||||
while mask != 0 {
|
||||
let trailing_zeros = mask.trailing_zeros() as usize;
|
||||
let ptr2 = ptr.add(trailing_zeros);
|
||||
|
@ -138,11 +111,10 @@ pub unsafe fn escape_aligned(
|
|||
}
|
||||
mask ^= 1 << trailing_zeros;
|
||||
}
|
||||
|
||||
ptr = next_ptr;
|
||||
}
|
||||
|
||||
debug_assert!(ptr <= end_ptr);
|
||||
debug_assert!(start_ptr <= ptr);
|
||||
naive::escape(buffer, start_ptr, ptr, end_ptr);
|
||||
if end_ptr > start_ptr {
|
||||
let slc = slice::from_raw_parts(start_ptr, end_ptr as usize - start_ptr as usize);
|
||||
buffer.push_str(std::str::from_utf8_unchecked(slc));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,512 +0,0 @@
|
|||
use std::ptr;
|
||||
|
||||
const DEC_DIGITS_LUT: &'static [u8] = b"\
|
||||
0001020304050607080910111213141516171819\
|
||||
2021222324252627282930313233343536373839\
|
||||
4041424344454647484950515253545556575859\
|
||||
6061626364656667686970717273747576777879\
|
||||
8081828384858687888990919293949596979899";
|
||||
|
||||
macro_rules! lookup {
|
||||
($idx:expr) => {
|
||||
DEC_DIGITS_LUT.as_ptr().add(($idx as usize) << 1)
|
||||
};
|
||||
}
|
||||
|
||||
/// write integer smaller than 10000
|
||||
#[inline]
|
||||
unsafe fn write_small(n: u16, buf: *mut u8) -> usize {
|
||||
debug_assert!(n < 10000);
|
||||
|
||||
if n < 100 {
|
||||
if n < 10 {
|
||||
*buf = n as u8 + 0x30;
|
||||
1
|
||||
} else {
|
||||
ptr::copy_nonoverlapping(lookup!(n), buf, 2);
|
||||
2
|
||||
}
|
||||
} else {
|
||||
if n < 1000 {
|
||||
let d1 = (n / 100) as u8;
|
||||
let d2 = n % 100;
|
||||
*buf = d1 + 0x30;
|
||||
ptr::copy_nonoverlapping(lookup!(d2), buf.add(1), 2);
|
||||
3
|
||||
} else {
|
||||
let d1 = n / 100;
|
||||
let d2 = n % 100;
|
||||
ptr::copy_nonoverlapping(lookup!(d1), buf, 2);
|
||||
ptr::copy_nonoverlapping(lookup!(d2), buf.add(2), 2);
|
||||
4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// write integer smaller with 0 padding
|
||||
#[inline]
|
||||
unsafe fn write_small_pad(n: u16, buf: *mut u8) {
|
||||
debug_assert!(n < 10000);
|
||||
|
||||
let d1 = n / 100;
|
||||
let d2 = n % 100;
|
||||
|
||||
ptr::copy_nonoverlapping(lookup!(d1), buf, 2);
|
||||
ptr::copy_nonoverlapping(lookup!(d2), buf.add(2), 2);
|
||||
}
|
||||
|
||||
unsafe fn write_u8(n: u8, buf: *mut u8) -> usize {
|
||||
if n < 10 {
|
||||
*buf = n + 0x30;
|
||||
1
|
||||
} else if n < 100 {
|
||||
ptr::copy_nonoverlapping(lookup!(n), buf, 2);
|
||||
2
|
||||
} else {
|
||||
let d1 = n / 100;
|
||||
let d2 = n % 100;
|
||||
*buf = d1 + 0x30;
|
||||
ptr::copy_nonoverlapping(lookup!(d2), buf.add(1), 2);
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_u16(n: u16, buf: *mut u8) -> usize {
|
||||
if n < 100 {
|
||||
if n < 10 {
|
||||
*buf = n as u8 + 0x30;
|
||||
1
|
||||
} else {
|
||||
ptr::copy_nonoverlapping(lookup!(n), buf, 2);
|
||||
2
|
||||
}
|
||||
} else if n < 10000 {
|
||||
if n < 1000 {
|
||||
let d1 = (n / 100) as u8;
|
||||
let d2 = n % 100;
|
||||
*buf = d1 + 0x30;
|
||||
ptr::copy_nonoverlapping(lookup!(d2), buf.add(1), 2);
|
||||
3
|
||||
} else {
|
||||
let d1 = n / 100;
|
||||
let d2 = n % 100;
|
||||
ptr::copy_nonoverlapping(lookup!(d1), buf, 2);
|
||||
ptr::copy_nonoverlapping(lookup!(d2), buf.add(2), 2);
|
||||
4
|
||||
}
|
||||
} else {
|
||||
let b = (n / 10000) as u8; // 1 to 6
|
||||
let c = n % 10000;
|
||||
|
||||
*buf = b + 0x30;
|
||||
write_small_pad(c, buf.add(1));
|
||||
5
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_u32(mut n: u32, buf: *mut u8) -> usize {
|
||||
if n < 10000 {
|
||||
write_small(n as u16, buf)
|
||||
} else if n < 100000000 {
|
||||
let b = n / 10000;
|
||||
let c = n % 10000;
|
||||
|
||||
let l = write_small(b as u16, buf);
|
||||
write_small_pad(c as u16, buf.add(l));
|
||||
l + 4
|
||||
} else {
|
||||
let a = n / 100000000; // 1 to 42
|
||||
n %= 100000000;
|
||||
|
||||
let l = if a >= 10 {
|
||||
ptr::copy_nonoverlapping(lookup!(a), buf, 2);
|
||||
2
|
||||
} else {
|
||||
*buf = a as u8 + 0x30;
|
||||
1
|
||||
};
|
||||
|
||||
let b = n / 10000;
|
||||
let c = n % 10000;
|
||||
|
||||
write_small_pad(b as u16, buf.add(l));
|
||||
write_small_pad(c as u16, buf.add(l + 4));
|
||||
l + 8
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_u64(mut n: u64, buf: *mut u8) -> usize {
|
||||
if n < 10000 {
|
||||
write_small(n as u16, buf)
|
||||
} else if n < 100000000 {
|
||||
let n = n as u32;
|
||||
let b = n / 10000;
|
||||
let c = n % 10000;
|
||||
|
||||
let l = write_small(b as u16, buf);
|
||||
write_small_pad(c as u16, buf.add(l));
|
||||
l + 4
|
||||
} else if n < 10000000000000000 {
|
||||
let v0 = n / 100000000;
|
||||
let v1 = (n % 100000000) as u32;
|
||||
|
||||
let l = if v0 < 10000 {
|
||||
write_small(v0 as u16, buf)
|
||||
} else {
|
||||
let b0 = v0 / 10000;
|
||||
let c0 = v0 % 10000;
|
||||
let l = write_small(b0 as u16, buf);
|
||||
write_small_pad(c0 as u16, buf.add(l));
|
||||
l + 4
|
||||
};
|
||||
|
||||
let b1 = v1 / 10000;
|
||||
let c1 = v1 % 10000;
|
||||
|
||||
write_small_pad(b1 as u16, buf.add(l));
|
||||
write_small_pad(c1 as u16, buf.add(l + 4));
|
||||
|
||||
l + 8
|
||||
} else {
|
||||
let a = n / 10000000000000000; // 1 to 1844
|
||||
n %= 10000000000000000;
|
||||
|
||||
let v0 = (n / 100000000) as u32;
|
||||
let v1 = (n % 100000000) as u32;
|
||||
|
||||
let b0 = v0 / 10000;
|
||||
let c0 = v0 % 10000;
|
||||
|
||||
let b1 = v1 / 10000;
|
||||
let c1 = v1 % 10000;
|
||||
|
||||
let l = write_small(a as u16, buf);
|
||||
write_small_pad(b0 as u16, buf.add(l));
|
||||
write_small_pad(c0 as u16, buf.add(l + 4));
|
||||
write_small_pad(b1 as u16, buf.add(l + 8));
|
||||
write_small_pad(c1 as u16, buf.add(l + 12));
|
||||
l + 16
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn write_u128(n: u128, buf: *mut u8) -> usize {
|
||||
if n <= std::u64::MAX as u128 {
|
||||
write_u64(n as u64, buf)
|
||||
} else if n < 100000000000000000000000000000000 {
|
||||
let a0 = (n / 10000000000000000) as u64;
|
||||
let a1 = (n % 10000000000000000) as u64;
|
||||
|
||||
let b0 = (a1 / 100000000) as u32;
|
||||
let b1 = (a1 / 100000000) as u32;
|
||||
|
||||
let c0 = (b0 / 10000) as u16;
|
||||
let c1 = (b0 % 10000) as u16;
|
||||
let c2 = (b1 / 10000) as u16;
|
||||
let c3 = (b1 % 10000) as u16;
|
||||
|
||||
let l = write_u64(a0, buf);
|
||||
write_small_pad(c0, buf.add(l));
|
||||
write_small_pad(c1, buf.add(l + 4));
|
||||
write_small_pad(c2, buf.add(l + 8));
|
||||
write_small_pad(c3, buf.add(l + 12));
|
||||
l + 16
|
||||
} else {
|
||||
let a0 = (n / 100000000000000000000000000000000) as u32; // 1 to 3402823
|
||||
let a1 = n % 100000000000000000000000000000000;
|
||||
|
||||
let b0 = (a1 / 10000000000000000) as u64;
|
||||
let b1 = (a1 % 10000000000000000) as u64;
|
||||
|
||||
let c0 = (b0 / 100000000) as u32;
|
||||
let c1 = (b0 % 100000000) as u32;
|
||||
let c2 = (b1 / 100000000) as u32;
|
||||
let c3 = (b1 % 100000000) as u32;
|
||||
|
||||
let d0 = (c0 / 10000) as u16;
|
||||
let d1 = (c0 % 10000) as u16;
|
||||
let d2 = (c1 / 10000) as u16;
|
||||
let d3 = (c1 % 10000) as u16;
|
||||
let d4 = (c2 / 10000) as u16;
|
||||
let d5 = (c2 % 10000) as u16;
|
||||
let d6 = (c3 / 10000) as u16;
|
||||
let d7 = (c3 % 10000) as u16;
|
||||
|
||||
let l = if a0 < 10000 {
|
||||
write_small(a0 as u16, buf)
|
||||
} else {
|
||||
let b0 = (a0 / 10000) as u16;
|
||||
let b1 = (a0 % 10000) as u16;
|
||||
let l = write_small(b0, buf);
|
||||
write_small_pad(b1, buf.add(l));
|
||||
l + 4
|
||||
};
|
||||
|
||||
write_small_pad(d0, buf.add(l));
|
||||
write_small_pad(d1, buf.add(l + 4));
|
||||
write_small_pad(d2, buf.add(l + 8));
|
||||
write_small_pad(d3, buf.add(l + 12));
|
||||
write_small_pad(d4, buf.add(l + 16));
|
||||
write_small_pad(d5, buf.add(l + 20));
|
||||
write_small_pad(d6, buf.add(l + 24));
|
||||
write_small_pad(d7, buf.add(l + 28));
|
||||
|
||||
l + 32
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Integer {
|
||||
const MAX_LEN: usize;
|
||||
unsafe fn write_to(self, buf: *mut u8) -> usize;
|
||||
}
|
||||
|
||||
macro_rules! impl_integer {
|
||||
($unsigned:ty, $signed:ty, $conv:ty, $func:ident, $max_len:expr) => {
|
||||
impl Integer for $unsigned {
|
||||
const MAX_LEN: usize = $max_len;
|
||||
|
||||
#[inline]
|
||||
unsafe fn write_to(self, buf: *mut u8) -> usize {
|
||||
$func(self as $conv, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Integer for $signed {
|
||||
const MAX_LEN: usize = $max_len + 1;
|
||||
|
||||
#[inline]
|
||||
unsafe fn write_to(self, mut buf: *mut u8) -> usize {
|
||||
if self >= 0 {
|
||||
$func(self as $conv, buf)
|
||||
} else {
|
||||
*buf = b'-';
|
||||
buf = buf.add(1);
|
||||
let n = (!(self as $conv)).wrapping_add(1);
|
||||
$func(n, buf) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_integer!(u8, i8, u8, write_u8, 3);
|
||||
impl_integer!(u16, i16, u16, write_u16, 5);
|
||||
impl_integer!(u32, i32, u32, write_u32, 10);
|
||||
impl_integer!(u64, i64, u64, write_u64, 20);
|
||||
impl_integer!(u128, i128, u128, write_u128, 39);
|
||||
|
||||
#[cfg(target_pointer_width = "16")]
|
||||
impl_integer!(usize, isize, u16, write_u16, 6);
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
impl_integer!(usize, isize, u32, write_u32, 11);
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
impl_integer!(usize, isize, u64, write_u64, 20);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// comprehenisive test
|
||||
#[test]
|
||||
fn test_i8_all() {
|
||||
use super::Integer;
|
||||
let mut buf = Vec::with_capacity(i8::MAX_LEN);
|
||||
|
||||
for n in std::i8::MIN..=std::i8::MAX {
|
||||
unsafe {
|
||||
let l = n.write_to(buf.as_mut_ptr());
|
||||
buf.set_len(l);
|
||||
assert_eq!(std::str::from_utf8_unchecked(&*buf), format!("{}", n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// random test
|
||||
#[test]
|
||||
fn test_u64_random() {
|
||||
use super::Integer;
|
||||
let mut buf = Vec::with_capacity(u64::MAX_LEN);
|
||||
|
||||
let mut state = 88172645463325252u64;
|
||||
|
||||
for _ in 0..100 {
|
||||
// xorshift
|
||||
state ^= state << 13;
|
||||
state ^= state >> 7;
|
||||
state ^= state << 17;
|
||||
|
||||
unsafe {
|
||||
let l = state.write_to(buf.as_mut_ptr());
|
||||
buf.set_len(l);
|
||||
assert_eq!(std::str::from_utf8_unchecked(&*buf), format!("{}", state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! make_test {
|
||||
($name:ident, $type:ty, $($value:expr),*) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
use super::Integer;
|
||||
|
||||
unsafe fn test_write(val: $type, buf: &mut Vec<u8>) {
|
||||
let l = val.write_to(buf.as_mut_ptr());
|
||||
buf.set_len(l);
|
||||
assert_eq!(
|
||||
std::str::from_utf8_unchecked(&*buf),
|
||||
format!("{}", val)
|
||||
);
|
||||
}
|
||||
|
||||
let mut buf = Vec::with_capacity(<$type>::MAX_LEN);
|
||||
unsafe {
|
||||
$(
|
||||
test_write($value as $type, &mut buf);
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// boundary tests
|
||||
make_test!(test_u8, u8, 0, 1, 9, 10, 99, 100, 254, 255);
|
||||
make_test!(test_u16, u16, 0, 9, 10, 99, 100, 999, 1000, 9999, 10000, 65535);
|
||||
make_test!(
|
||||
test_u32, u32, 0, 9, 10, 99, 100, 999, 1000, 9999, 10000, 99999, 100000, 999999,
|
||||
1000000, 9999999, 10000000, 99999999, 100000000, 999999999, 1000000000,
|
||||
4294967295
|
||||
);
|
||||
make_test!(
|
||||
test_u64,
|
||||
u64,
|
||||
0,
|
||||
9,
|
||||
10,
|
||||
99,
|
||||
100,
|
||||
999,
|
||||
1000,
|
||||
9999,
|
||||
10000,
|
||||
99999,
|
||||
100000,
|
||||
999999,
|
||||
1000000,
|
||||
9999999,
|
||||
10000000,
|
||||
99999999,
|
||||
100000000,
|
||||
999999999,
|
||||
1000000000,
|
||||
9999999999,
|
||||
10000000000,
|
||||
99999999999,
|
||||
100000000000,
|
||||
999999999999,
|
||||
1000000000000,
|
||||
9999999999999,
|
||||
10000000000000,
|
||||
99999999999999,
|
||||
100000000000000,
|
||||
999999999999999,
|
||||
1000000000000000,
|
||||
9999999999999999,
|
||||
10000000000000000,
|
||||
99999999999999999,
|
||||
100000000000000000,
|
||||
999999999999999999,
|
||||
1000000000000000000,
|
||||
9999999999999999999,
|
||||
10000000000000000000,
|
||||
18446744073709551615
|
||||
);
|
||||
|
||||
make_test!(
|
||||
test_u128,
|
||||
u128,
|
||||
0,
|
||||
9,
|
||||
10,
|
||||
99,
|
||||
100,
|
||||
999,
|
||||
1000,
|
||||
9999,
|
||||
10000,
|
||||
99999,
|
||||
100000,
|
||||
999999,
|
||||
1000000,
|
||||
9999999,
|
||||
10000000,
|
||||
99999999,
|
||||
100000000,
|
||||
999999999,
|
||||
1000000000,
|
||||
9999999999,
|
||||
10000000000,
|
||||
99999999999,
|
||||
100000000000,
|
||||
999999999999,
|
||||
1000000000000,
|
||||
9999999999999,
|
||||
10000000000000,
|
||||
99999999999999,
|
||||
100000000000000,
|
||||
999999999999999,
|
||||
1000000000000000,
|
||||
9999999999999999,
|
||||
10000000000000000,
|
||||
99999999999999999,
|
||||
100000000000000000,
|
||||
999999999999999999,
|
||||
1000000000000000000,
|
||||
9999999999999999999,
|
||||
10000000000000000000,
|
||||
99999999999999999999,
|
||||
100000000000000000000,
|
||||
999999999999999999999,
|
||||
1000000000000000000000,
|
||||
9999999999999999999999,
|
||||
10000000000000000000000,
|
||||
99999999999999999999999,
|
||||
100000000000000000000000,
|
||||
100000000000000000000000,
|
||||
999999999999999999999999,
|
||||
1000000000000000000000000,
|
||||
9999999999999999999999999,
|
||||
10000000000000000000000000,
|
||||
99999999999999999999999999,
|
||||
100000000000000000000000000,
|
||||
999999999999999999999999999,
|
||||
1000000000000000000000000000,
|
||||
9999999999999999999999999999,
|
||||
10000000000000000000000000000,
|
||||
99999999999999999999999999999,
|
||||
100000000000000000000000000000,
|
||||
999999999999999999999999999999,
|
||||
1000000000000000000000000000000,
|
||||
9999999999999999999999999999999,
|
||||
10000000000000000000000000000000,
|
||||
99999999999999999999999999999999,
|
||||
100000000000000000000000000000000,
|
||||
999999999999999999999999999999999,
|
||||
1000000000000000000000000000000000,
|
||||
9999999999999999999999999999999999,
|
||||
10000000000000000000000000000000000,
|
||||
99999999999999999999999999999999999,
|
||||
100000000000000000000000000000000000,
|
||||
999999999999999999999999999999999999,
|
||||
1000000000000000000000000000000000000,
|
||||
9999999999999999999999999999999999999,
|
||||
10000000000000000000000000000000000000,
|
||||
99999999999999999999999999999999999999,
|
||||
100000000000000000000000000000000000000,
|
||||
340282366920938463463374607431768211455
|
||||
);
|
||||
|
||||
make_test!(test_i8, i8, std::i8::MIN, std::i8::MAX);
|
||||
make_test!(test_i16, i16, std::i16::MIN, std::i16::MAX);
|
||||
make_test!(test_i32, i32, std::i32::MIN, std::i32::MAX);
|
||||
make_test!(test_i64, i64, std::i64::MIN, std::i64::MAX);
|
||||
make_test!(test_i128, i128, std::i128::MIN, std::i128::MAX);
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
//! Sailfish runtime
|
||||
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
mod buffer;
|
||||
pub mod escape;
|
||||
mod integer;
|
||||
mod macros;
|
||||
mod render;
|
||||
mod size_hint;
|
||||
|
@ -30,6 +32,7 @@ pub struct RenderError {
|
|||
}
|
||||
|
||||
impl RenderError {
|
||||
/// Construct a new error with custom message
|
||||
pub fn new(msg: &str) -> Self {
|
||||
Self {
|
||||
kind: RenderErrorKind::Msg(msg.to_owned()),
|
||||
|
@ -66,6 +69,7 @@ impl From<fmt::Error> for RenderError {
|
|||
|
||||
pub type RenderResult = Result<String, RenderError>;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Context {
|
||||
#[doc(hidden)]
|
||||
pub buf: Buffer,
|
||||
|
|
|
@ -23,13 +23,15 @@ use super::{escape, RenderError};
|
|||
/// }
|
||||
/// ```
|
||||
pub trait Render {
|
||||
/// render to `Buffer` without escaping
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError>;
|
||||
|
||||
/// render to `Buffer` with HTML escaping
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
self.render(&mut tmp)?;
|
||||
b.push_str(tmp.as_str());
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +87,7 @@ impl Render for char {
|
|||
'&' => b.push_str("&"),
|
||||
'<' => b.push_str("<"),
|
||||
'>' => b.push_str(">"),
|
||||
'\'' => b.push_str("'"),
|
||||
_ => b.push(*self),
|
||||
}
|
||||
Ok(())
|
||||
|
@ -148,17 +151,18 @@ macro_rules! render_int {
|
|||
($($int:ty),*) => {
|
||||
$(
|
||||
impl Render for $int {
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use super::integer::Integer;
|
||||
use itoap::Integer;
|
||||
|
||||
b.reserve(Self::MAX_LEN);
|
||||
|
||||
unsafe {
|
||||
let ptr = b.as_mut_ptr().add(b.len());
|
||||
let l = self.write_to(ptr);
|
||||
let l = itoap::write_to_ptr(ptr, *self);
|
||||
b.set_len(b.len() + l);
|
||||
}
|
||||
debug_assert!(b.len() <= b.capacity());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -175,14 +179,15 @@ macro_rules! render_int {
|
|||
render_int!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize);
|
||||
|
||||
impl Render for f32 {
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
if self.is_finite() {
|
||||
if likely!(self.is_finite()) {
|
||||
unsafe {
|
||||
b.reserve(16);
|
||||
let ptr = b.as_mut_ptr().add(b.len());
|
||||
let l = ryu::raw::format32(*self, ptr);
|
||||
b.set_len(b.len() + l);
|
||||
debug_assert!(b.len() <= b.capacity());
|
||||
}
|
||||
} else if self.is_nan() {
|
||||
b.push_str("NaN");
|
||||
|
@ -203,14 +208,15 @@ impl Render for f32 {
|
|||
}
|
||||
|
||||
impl Render for f64 {
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
if self.is_finite() {
|
||||
if likely!(self.is_finite()) {
|
||||
unsafe {
|
||||
b.reserve(24);
|
||||
let ptr = b.as_mut_ptr().add(b.len());
|
||||
let l = ryu::raw::format64(*self, ptr);
|
||||
b.set_len(b.len() + l);
|
||||
debug_assert!(b.len() <= b.capacity());
|
||||
}
|
||||
} else if self.is_nan() {
|
||||
b.push_str("NaN");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
/// Dynamically updated size hint
|
||||
#[derive(Debug, Default)]
|
||||
#[doc(hidden)]
|
||||
pub struct SizeHint {
|
||||
value: AtomicUsize,
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ impl SizeHint {
|
|||
/// as the value passed on update()
|
||||
#[inline]
|
||||
pub fn update(&self, mut value: usize) {
|
||||
value = value + value / 8;
|
||||
if self.get() < value {
|
||||
value = value + value / 4;
|
||||
if unlikely!(self.get() < value) {
|
||||
self.value.store(value, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
use std::ptr;
|
||||
|
||||
#[cfg(sailfish_nightly)]
|
||||
macro_rules! likely {
|
||||
($val:expr) => {
|
||||
std::intrinsics::likely($val)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(sailfish_nightly))]
|
||||
macro_rules! likely {
|
||||
($val:expr) => {
|
||||
$val
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(sailfish_nightly)]
|
||||
macro_rules! unlikely {
|
||||
($val:expr) => {
|
||||
std::intrinsics::unlikely($val)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(sailfish_nightly))]
|
||||
macro_rules! unlikely {
|
||||
($val:expr) => {
|
||||
$val
|
||||
};
|
||||
}
|
||||
|
||||
/// memcpy implementation based on glibc (https://github.molgen.mpg.de/git-mirror/glibc/blob/master/sysdeps/x86_64/multiarch/memcpy-avx-unaligned.S)
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn memcpy_16(src: *const u8, dst: *mut u8, len: usize) {
|
||||
debug_assert!(len <= 16);
|
||||
let len_u8 = len as u8;
|
||||
|
||||
if len_u8 >= 8 {
|
||||
let offset = len - 8;
|
||||
let t2 = ptr::read_unaligned(src.add(offset) as *const u64);
|
||||
let t1 = ptr::read_unaligned(src as *const u64);
|
||||
ptr::write_unaligned(dst.add(offset) as *mut u64, t2);
|
||||
ptr::write_unaligned(dst as *mut u64, t1);
|
||||
} else if len_u8 >= 4 {
|
||||
let offset = len - 4;
|
||||
let t2 = ptr::read_unaligned(src.add(offset) as *const u32);
|
||||
let t1 = ptr::read_unaligned(src as *const u32);
|
||||
ptr::write_unaligned(dst.add(offset) as *mut u32, t2);
|
||||
ptr::write_unaligned(dst as *mut u32, t1);
|
||||
} else if len_u8 >= 2 {
|
||||
let offset = len - 2;
|
||||
let t2 = ptr::read_unaligned(src.add(offset) as *const u16);
|
||||
let t1 = ptr::read_unaligned(src as *const u16);
|
||||
ptr::write_unaligned(dst.add(offset) as *mut u16, t2);
|
||||
ptr::write_unaligned(dst as *mut u16, t1);
|
||||
} else if len_u8 >= 1 {
|
||||
*dst = *src;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
# Syntax Highlighting for Sailfish Templates in Vim
|
||||
|
||||
## Installation
|
||||
|
||||
### Using dein.vim
|
||||
|
||||
```
|
||||
call dein#add('Kogia-sima/sailfish', {'rtp': 'syntax/vim'})
|
||||
```
|
||||
|
||||
### Using vim-plug
|
||||
|
||||
```
|
||||
Plug 'Kogia-sima/sailfish', { 'rtp': 'syntax/vim' }
|
||||
```
|
||||
|
||||
## Screenshot
|
||||
|
||||
![Screenshot](./screenshot.png)
|
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
|
@ -1 +1,2 @@
|
|||
/test.stpl
|
||||
*.vsix
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.vscode/**
|
||||
.vscode-test/**
|
||||
.gitignore
|
||||
*.vsix
|
||||
vsc-extension-quickstart.md
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
This directory contains Syntax Highlighting extension for sailfish templates in Visual Studio Code.
|
||||
|
||||
The extension is available at [VisualStudio Marketplace](https://marketplace.visualstudio.com/items?itemName=kogia-sima.vscode-sailfish).
|
||||
|
||||
## Features
|
||||
|
||||
- Full Rust syntax highlighting rules inside code blocks
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
"description": "Syntax highlighting for sailfish templates in VSCode",
|
||||
"version": "0.1.0",
|
||||
"author": "Ryohei Machida <orcinus4627@gmail.com>",
|
||||
"publisher": "kogia-sima",
|
||||
"repository": "Kogia-sima/sailfish",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.45.0"
|
||||
|
|
Loading…
Reference in New Issue