feat: add multi-engine regex flavour support

This commit is contained in:
2026-07-27 11:43:51 +02:00
parent 7079cde15f
commit 7f3a91ad37
340 changed files with 643286 additions and 483 deletions

216
engines/rust/Cargo.lock generated Normal file
View File

@@ -0,0 +1,216 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "regex-tools-rust-engine"
version = "0.1.0"
dependencies = [
"regex",
"serde",
"serde_json",
"wasm-bindgen",
]
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasm-bindgen"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

23
engines/rust/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "regex-tools-rust-engine"
version = "0.1.0"
edition = "2024"
rust-version = "1.97.1"
license = "GPL-3.0-or-later"
publish = false
[lib]
crate-type = ["cdylib"]
[dependencies]
regex = "=1.13.1"
serde = { version = "=1.0.228", features = ["derive"] }
serde_json = "=1.0.150"
wasm-bindgen = "=0.2.126"
[profile.release]
codegen-units = 1
lto = true
opt-level = "z"
panic = "abort"
strip = "symbols"

25
engines/rust/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Rust regex WebAssembly engine
This directory builds the actual Rust `regex` crate 1.13.1 for
`wasm32-unknown-unknown` with Rust 1.97.1 and wasm-bindgen 0.2.126.
The fixed bridge accepts JSON values only. The adapter measures the exact
encoded request size, and the native cap includes JSON's worst-case sixfold
escaping for every accepted string field. It exposes native UTF-8 byte offsets,
capture names, iteration, and `Captures::expand` replacement syntax. It does
not evaluate user values as Rust or JavaScript source.
Replacement uses a bounded streaming implementation of
`Captures::expand` grammar. Load-time differential fixtures compare it with
the native API, and capture references stop expanding as soon as the UTF-8
output cap is reached.
The bounded bytes cross the JSON bridge as base64, so control-heavy output has
a fixed 4:3 transport expansion instead of JSON's content-dependent escaping.
Flags `i`, `m`, `s`, `U`, `x`, and `R` map directly to `RegexBuilder`.
Unicode is the crate's native default; the accepted `u` flag explicitly
affirms that default. `g` selects the crate's native iterator.
The crate deliberately excludes look-around and backreferences. Compile-size,
DFA-cache, pattern, subject, match, capture-row, and output limits are enforced
inside the module. Replacement output is clipped only at a UTF-8 boundary.

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "1.97.1"
profile = "minimal"
targets = ["wasm32-unknown-unknown"]

574
engines/rust/src/lib.rs Normal file
View File

@@ -0,0 +1,574 @@
//! Fixed, bounded WebAssembly bridge for Rust's `regex` crate 1.13.1.
//!
//! User-controlled pattern, subject and replacement values enter through
//! deserialized JSON. They are never interpreted as Rust or JavaScript source.
#![forbid(unsafe_code)]
use regex::{Captures, Regex, RegexBuilder};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::{BTreeMap, BTreeSet};
use wasm_bindgen::prelude::*;
const BRIDGE_ABI_VERSION: u32 = 1;
const REGEX_CRATE_VERSION: &str = "1.13.1";
const RUSTC_VERSION: &str = env!("REGEX_TOOLS_RUSTC_VERSION");
const EXPECTED_RUSTC_VERSION: &str = "rustc 1.97.1 (8bab26f4f 2026-07-14)";
const MAXIMUM_PATTERN_BYTES: usize = 256 * 1024;
const MAXIMUM_SUBJECT_BYTES: usize = 16 * 1024 * 1024;
const MAXIMUM_REPLACEMENT_BYTES: usize = 256 * 1024;
const MAXIMUM_MATCHES: usize = 10_000;
const MAXIMUM_CAPTURE_ROWS: usize = 100_000;
const MAXIMUM_CAPTURE_GROUPS: usize = 1_000;
const MAXIMUM_OUTPUT_BYTES: usize = 64 * 1024 * 1024;
const MAXIMUM_REQUEST_JSON_BYTES: usize =
6 * (MAXIMUM_SUBJECT_BYTES + MAXIMUM_PATTERN_BYTES + MAXIMUM_REPLACEMENT_BYTES)
+ 64 * 1024;
const MAXIMUM_ERROR_MESSAGE_BYTES: usize = 1_024;
const COMPILED_SIZE_LIMIT_BYTES: usize = 10 * 1024 * 1024;
const DFA_SIZE_LIMIT_BYTES: usize = 2 * 1024 * 1024;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct BridgeRequest {
abi: u32,
operation: String,
pattern: String,
flags: String,
options: BTreeMap<String, Value>,
subject: String,
scan_all: bool,
maximum_matches: usize,
maximum_capture_rows: usize,
replacement: Option<String>,
maximum_output_bytes: Option<usize>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct BridgeFailure {
abi: u32,
ok: bool,
phase: &'static str,
code: &'static str,
message: String,
}
#[derive(Serialize)]
struct NativeMatch {
span: [usize; 2],
captures: Vec<Option<[usize; 2]>>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct BridgeSuccess {
abi: u32,
ok: bool,
group_count: usize,
group_names: Vec<(usize, String)>,
matches: Vec<NativeMatch>,
results_truncated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
output_base64: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
output_bytes: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
output_truncated: Option<bool>,
}
fn bounded_message(value: impl std::fmt::Display) -> String {
let value = value.to_string();
if value.len() <= MAXIMUM_ERROR_MESSAGE_BYTES {
return value;
}
let mut end = MAXIMUM_ERROR_MESSAGE_BYTES - "".len();
while !value.is_char_boundary(end) {
end -= 1;
}
format!("{}", &value[..end])
}
fn serialize<T: Serialize>(value: &T) -> String {
match serde_json::to_string(value) {
Ok(encoded) => encoded,
Err(_) => {
"{\"abi\":1,\"ok\":false,\"phase\":\"bridge\",\"code\":\"serialization-error\",\"message\":\"The Rust bridge could not serialize its bounded result.\"}".to_owned()
}
}
}
fn failure(phase: &'static str, code: &'static str, message: impl std::fmt::Display) -> String {
serialize(&BridgeFailure {
abi: BRIDGE_ABI_VERSION,
ok: false,
phase,
code,
message: bounded_message(message),
})
}
fn validate_request(request: &BridgeRequest) -> Result<(), String> {
if request.abi != BRIDGE_ABI_VERSION {
return Err(failure(
"bridge",
"abi-mismatch",
"The bridge ABI version is unsupported.",
));
}
if request.operation != "execute" && request.operation != "replace" {
return Err(failure(
"bridge",
"invalid-operation",
"The bridge operation is unsupported.",
));
}
if !request.options.is_empty() {
return Err(failure(
"bridge",
"unsupported-option",
"Rust regex does not accept engine options.",
));
}
if request.pattern.len() > MAXIMUM_PATTERN_BYTES {
return Err(failure(
"bridge",
"pattern-limit",
"The pattern exceeds the bridge limit.",
));
}
if request.subject.len() > MAXIMUM_SUBJECT_BYTES {
return Err(failure(
"bridge",
"subject-limit",
"The subject exceeds the bridge limit.",
));
}
if request.maximum_matches == 0
|| request.maximum_matches > MAXIMUM_MATCHES
|| request.maximum_capture_rows == 0
|| request.maximum_capture_rows > MAXIMUM_CAPTURE_ROWS
{
return Err(failure(
"bridge",
"result-limit",
"The requested result bounds are outside the bridge contract.",
));
}
let mut seen = BTreeSet::new();
for flag in request.flags.chars() {
if !"gimsUuxR".contains(flag) || !seen.insert(flag) {
return Err(failure(
"bridge",
"unsupported-flag",
"The request contains an unsupported or duplicate Rust flag.",
));
}
}
if request.operation == "execute" {
if request.replacement.is_some() || request.maximum_output_bytes.is_some() {
return Err(failure(
"bridge",
"invalid-request",
"Execution requests cannot contain replacement fields.",
));
}
return Ok(());
}
let Some(replacement) = &request.replacement else {
return Err(failure(
"bridge",
"invalid-request",
"Replacement requests require a template.",
));
};
let Some(output_limit) = request.maximum_output_bytes else {
return Err(failure(
"bridge",
"invalid-request",
"Replacement requests require an output bound.",
));
};
if replacement.len() > MAXIMUM_REPLACEMENT_BYTES {
return Err(failure(
"bridge",
"replacement-limit",
"The replacement exceeds the bridge limit.",
));
}
if output_limit == 0 || output_limit > MAXIMUM_OUTPUT_BYTES {
return Err(failure(
"bridge",
"output-limit",
"The output bound is outside the bridge contract.",
));
}
Ok(())
}
fn compile(request: &BridgeRequest) -> Result<Regex, String> {
let has = |flag| request.flags.contains(flag);
let mut builder = RegexBuilder::new(&request.pattern);
builder
.case_insensitive(has('i'))
.multi_line(has('m'))
.dot_matches_new_line(has('s'))
.swap_greed(has('U'))
// Unicode is Rust regex's native default. The accepted `u` flag is an
// explicit affirmation, not a request to disable Unicode when absent.
.unicode(true)
.ignore_whitespace(has('x'))
.crlf(has('R'))
.size_limit(COMPILED_SIZE_LIMIT_BYTES)
.dfa_size_limit(DFA_SIZE_LIMIT_BYTES);
builder
.build()
.map_err(|error| failure("compile", "compile-error", error))
}
fn materialize(captures: &Captures<'_>, group_count: usize) -> NativeMatch {
let whole = captures
.get(0)
.expect("regex captures always contain the complete match");
let groups = (1..=group_count)
.map(|group| {
captures
.get(group)
.map(|capture| [capture.start(), capture.end()])
})
.collect();
NativeMatch {
span: [whole.start(), whole.end()],
captures: groups,
}
}
fn collect_matches(
compiled: &Regex,
request: &BridgeRequest,
group_count: usize,
) -> (Vec<NativeMatch>, bool) {
let rows_per_match = group_count.max(1);
let allowed = request
.maximum_matches
.min(request.maximum_capture_rows / rows_per_match);
let global = request.flags.contains('g');
let mut records = Vec::with_capacity(allowed.min(256));
let mut truncated = false;
if global {
for captures in compiled.captures_iter(&request.subject) {
if records.len() == allowed {
truncated = true;
break;
}
records.push(materialize(&captures, group_count));
}
} else if let Some(captures) = compiled.captures(&request.subject) {
if allowed == 0 {
truncated = true;
} else {
records.push(materialize(&captures, group_count));
}
}
(records, truncated)
}
struct BoundedOutput {
value: String,
maximum: usize,
truncated: bool,
}
impl BoundedOutput {
fn new(maximum: usize) -> Self {
Self {
value: String::with_capacity(maximum.min(64 * 1024)),
maximum,
truncated: false,
}
}
fn append(&mut self, value: &str) {
if self.truncated || value.is_empty() {
return;
}
let remaining = self.maximum - self.value.len();
if value.len() <= remaining {
self.value.push_str(value);
return;
}
let mut end = remaining;
while !value.is_char_boundary(end) {
end -= 1;
}
self.value.push_str(&value[..end]);
self.truncated = true;
}
}
enum ReplacementReference<'a> {
Number(usize),
Named(&'a str),
}
fn capture_reference(replacement: &str) -> Option<(ReplacementReference<'_>, usize)> {
let bytes = replacement.as_bytes();
if bytes.len() <= 1 || bytes[0] != b'$' {
return None;
}
let (name, end) = if bytes[1] == b'{' {
let close = bytes[2..].iter().position(|&byte| byte == b'}')? + 2;
(&replacement[2..close], close + 1)
} else {
let mut end = 1;
while bytes
.get(end)
.is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'_')
{
end += 1;
}
if end == 1 {
return None;
}
(&replacement[1..end], end)
};
let reference = match name.parse::<usize>() {
Ok(number) => ReplacementReference::Number(number),
Err(_) => ReplacementReference::Named(name),
};
Some((reference, end))
}
fn expand_replacement_bounded(
output: &mut BoundedOutput,
captures: &Captures<'_>,
mut replacement: &str,
) {
while !replacement.is_empty() && !output.truncated {
let Some(dollar) = replacement.as_bytes().iter().position(|&byte| byte == b'$') else {
output.append(replacement);
return;
};
output.append(&replacement[..dollar]);
if output.truncated {
return;
}
replacement = &replacement[dollar..];
if replacement.as_bytes().get(1) == Some(&b'$') {
output.append("$");
replacement = &replacement[2..];
continue;
}
let Some((reference, end)) = capture_reference(replacement) else {
output.append("$");
replacement = &replacement[1..];
continue;
};
replacement = &replacement[end..];
let capture = match reference {
ReplacementReference::Number(number) => captures.get(number),
ReplacementReference::Named(name) => captures.name(name),
};
if let Some(capture) = capture {
output.append(capture.as_str());
}
}
}
fn encode_base64(input: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut encoded = String::with_capacity(input.len().div_ceil(3) * 4);
for chunk in input.chunks(3) {
let first = chunk[0];
let second = chunk.get(1).copied().unwrap_or(0);
let third = chunk.get(2).copied().unwrap_or(0);
encoded.push(ALPHABET[usize::from(first >> 2)] as char);
encoded.push(ALPHABET[usize::from(((first & 0x03) << 4) | (second >> 4))] as char);
encoded.push(if chunk.len() > 1 {
ALPHABET[usize::from(((second & 0x0f) << 2) | (third >> 6))] as char
} else {
'='
});
encoded.push(if chunk.len() > 2 {
ALPHABET[usize::from(third & 0x3f)] as char
} else {
'='
});
}
encoded
}
fn replace_bounded(
compiled: &Regex,
request: &BridgeRequest,
accepted_matches: usize,
) -> (String, usize, bool) {
let replacement = request
.replacement
.as_deref()
.expect("validated replacement request");
let output_limit = request
.maximum_output_bytes
.expect("validated replacement request");
let mut output = BoundedOutput::new(output_limit);
let mut cursor = 0;
let global = request.flags.contains('g');
let maximum = if global {
accepted_matches
} else {
accepted_matches.min(1)
};
for captures in compiled.captures_iter(&request.subject).take(maximum) {
let whole = captures
.get(0)
.expect("regex captures always contain the complete match");
output.append(&request.subject[cursor..whole.start()]);
expand_replacement_bounded(&mut output, &captures, replacement);
cursor = whole.end();
}
output.append(&request.subject[cursor..]);
let bytes = output.value.len();
(
encode_base64(output.value.as_bytes()),
bytes,
output.truncated,
)
}
fn execute(encoded: &str) -> String {
if encoded.len() > MAXIMUM_REQUEST_JSON_BYTES {
return failure(
"bridge",
"request-limit",
"The encoded request exceeds the bridge limit.",
);
}
let request: BridgeRequest = match serde_json::from_str(encoded) {
Ok(request) => request,
Err(error) => return failure("bridge", "invalid-request", error),
};
if let Err(error) = validate_request(&request) {
return error;
}
// Reading the field also keeps the transport contract explicit: matching
// is selected by the normalized native `g` flag, while scanAll records the
// application request that caused an internal `g` to be added.
let _scan_all = request.scan_all;
let compiled = match compile(&request) {
Ok(compiled) => compiled,
Err(error) => return error,
};
let group_count = compiled.captures_len() - 1;
if group_count > MAXIMUM_CAPTURE_GROUPS {
return failure(
"compile",
"capture-group-limit",
format!(
"The pattern declares {group_count} capture groups; the bridge limit is {MAXIMUM_CAPTURE_GROUPS}."
),
);
}
let group_names = compiled
.capture_names()
.enumerate()
.filter_map(|(number, name)| name.map(|name| (number, name.to_owned())))
.filter(|(number, _)| *number > 0)
.collect();
let (matches, results_truncated) = collect_matches(&compiled, &request, group_count);
let mut result = BridgeSuccess {
abi: BRIDGE_ABI_VERSION,
ok: true,
group_count,
group_names,
matches,
results_truncated,
output_base64: None,
output_bytes: None,
output_truncated: None,
};
if request.operation == "replace" {
let (output_base64, output_bytes, output_truncated) =
replace_bounded(&compiled, &request, result.matches.len());
result.output_base64 = Some(output_base64);
result.output_bytes = Some(output_bytes);
result.output_truncated = Some(output_truncated);
}
serialize(&result)
}
fn self_test() -> bool {
if RUSTC_VERSION != EXPECTED_RUSTC_VERSION || encode_base64("😀".as_bytes()) != "8J+YgA==" {
return false;
}
let Ok(compiled) = Regex::new(r"(?P<word>😀+)") else {
return false;
};
let Some(captures) = compiled.captures("x😀😀") else {
return false;
};
let Some(whole) = captures.get(0) else {
return false;
};
let Some(word) = captures.name("word") else {
return false;
};
if whole.start() != 1 || whole.end() != 9 || word.start() != 1 || word.end() != 9 {
return false;
}
for template in [
"${word}-$word-$$",
"$1x|${1}x",
"${missing}|$|${word",
"$01|${01}|$123456789012345678901234567890",
"${}|${word}",
] {
let mut native = String::new();
captures.expand(template, &mut native);
let mut bounded = BoundedOutput::new(4 * 1024);
expand_replacement_bounded(&mut bounded, &captures, template);
if bounded.truncated || bounded.value != native {
return false;
}
}
let mut adversarial = BoundedOutput::new(5);
expand_replacement_bounded(&mut adversarial, &captures, &"${word}".repeat(32 * 1024));
adversarial.truncated && adversarial.value == "😀"
}
#[wasm_bindgen]
pub fn identity() -> String {
let mut identity = Map::new();
identity.insert(
"bridge".to_owned(),
Value::String("regex-tools-rust-json".to_owned()),
);
identity.insert("bridgeVersion".to_owned(), Value::from(BRIDGE_ABI_VERSION));
identity.insert(
"engine".to_owned(),
Value::String("Rust regex crate".to_owned()),
);
identity.insert(
"engineVersion".to_owned(),
Value::String(REGEX_CRATE_VERSION.to_owned()),
);
identity.insert(
"offsetUnit".to_owned(),
Value::String("utf8-byte".to_owned()),
);
identity.insert(
"rustcVersion".to_owned(),
Value::String(RUSTC_VERSION.to_owned()),
);
identity.insert("selfTest".to_owned(), Value::Bool(self_test()));
let mut envelope = Map::new();
envelope.insert("abi".to_owned(), Value::from(BRIDGE_ABI_VERSION));
envelope.insert("ok".to_owned(), Value::Bool(true));
envelope.insert("identity".to_owned(), Value::Object(identity));
serialize(&Value::Object(envelope))
}
#[wasm_bindgen]
pub fn run(encoded: &str) -> String {
execute(encoded)
}