Port over regex implementation from fork, do some cleanup

This commit is contained in:
Filip Tibell 2024-04-20 22:47:27 +02:00
parent e11302766b
commit 12f5824da9
No known key found for this signature in database
6 changed files with 215 additions and 7 deletions

7
Cargo.lock generated
View file

@ -1522,6 +1522,7 @@ dependencies = [
"regex", "regex",
"reqwest", "reqwest",
"rustyline", "rustyline",
"self_cell",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
@ -2423,6 +2424,12 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "self_cell"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
[[package]] [[package]]
name = "semver" name = "semver"
version = "0.9.0" version = "0.9.0"

View file

@ -24,7 +24,6 @@ cli = [
"dep:env_logger", "dep:env_logger",
"dep:clap", "dep:clap",
"dep:include_dir", "dep:include_dir",
"dep:regex",
"dep:rustyline", "dep:rustyline",
"dep:zip_next", "dep:zip_next",
] ]
@ -76,7 +75,9 @@ path-clean = "1.0"
pathdiff = "0.2" pathdiff = "0.2"
pin-project = "1.0" pin-project = "1.0"
urlencoding = "2.1" urlencoding = "2.1"
bstr = "1.9.1" bstr = "1.9"
regex = "1.10"
self_cell = "1.0"
### RUNTIME ### RUNTIME
@ -133,10 +134,6 @@ env_logger = { optional = true, version = "0.11" }
itertools = "0.12" itertools = "0.12"
clap = { optional = true, version = "4.1", features = ["derive"] } clap = { optional = true, version = "4.1", features = ["derive"] }
include_dir = { optional = true, version = "0.7", features = ["glob"] } include_dir = { optional = true, version = "0.7", features = ["glob"] }
regex = { optional = true, version = "1.7", default-features = false, features = [
"std",
"unicode-perl",
] }
rustyline = { optional = true, version = "14.0" } rustyline = { optional = true, version = "14.0" }
zip_next = { optional = true, version = "1.1" } zip_next = { optional = true, version = "1.1" }

View file

@ -0,0 +1,76 @@
use std::sync::Arc;
use mlua::prelude::*;
use regex::{Captures, Regex};
use self_cell::self_cell;
use super::matches::LuaMatch;
type OptionalCaptures<'a> = Option<Captures<'a>>;
self_cell! {
struct LuaCapturesInner {
owner: Arc<String>,
#[covariant]
dependent: OptionalCaptures,
}
}
pub struct LuaCaptures {
inner: LuaCapturesInner,
}
impl LuaCaptures {
pub fn new(pattern: &Regex, text: String) -> Self {
Self {
inner: LuaCapturesInner::new(Arc::from(text), |owned| pattern.captures(owned.as_str())),
}
}
fn captures(&self) -> &Captures {
self.inner
.borrow_dependent()
.as_ref()
.expect("None captures should not be used")
}
fn num_captures(&self) -> usize {
// NOTE: Here we exclude the match for the entire regex
// pattern, only counting the named and numbered captures
self.captures().len() - 1
}
fn text(&self) -> Arc<String> {
Arc::clone(self.inner.borrow_owner())
}
}
impl LuaUserData for LuaCaptures {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("get", |_, this, n: usize| {
Ok(this
.captures()
.get(n)
.map(|m| LuaMatch::new(this.text(), m)))
});
methods.add_method("group", |_, this, group: String| {
Ok(this
.captures()
.name(&group)
.map(|m| LuaMatch::new(this.text(), m)))
});
methods.add_method("format", |_, this, format: String| {
let mut new = String::new();
this.captures().expand(&format, &mut new);
Ok(new)
});
methods.add_meta_method(LuaMetaMethod::Type, |_, _, ()| Ok("RegexCaptures"));
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.num_captures()));
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
Ok(format!("RegexCaptures({})", this.num_captures()))
});
}
}

View file

@ -0,0 +1,48 @@
use std::{ops::Range, sync::Arc};
use mlua::prelude::*;
use regex::Match;
pub struct LuaMatch {
text: Arc<String>,
start: usize,
end: usize,
}
impl LuaMatch {
pub fn new(text: Arc<String>, matched: Match) -> Self {
Self {
text,
start: matched.start(),
end: matched.end(),
}
}
fn range(&self) -> Range<usize> {
self.start..self.end
}
fn slice(&self) -> &str {
&self.text[self.range()]
}
}
impl LuaUserData for LuaMatch {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
// NOTE: Strings are 0 based in Rust but 1 based in Luau, and end of range in Rust is exclusive
fields.add_field_method_get("start", |_, this| Ok(this.start.saturating_add(1)));
fields.add_field_method_get("finish", |_, this| Ok(this.end));
fields.add_field_method_get("len", |_, this| Ok(this.range().len()));
fields.add_field_method_get("text", |_, this| Ok(this.slice().to_string()));
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("isEmpty", |_, this, ()| Ok(this.range().is_empty()));
methods.add_meta_method(LuaMetaMethod::Type, |_, _, ()| Ok("RegexMatch"));
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.range().len()));
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
Ok(format!("RegexMatch({})", this.slice()))
});
}
}

View file

@ -1,7 +1,21 @@
#![allow(clippy::module_inception)]
use mlua::prelude::*; use mlua::prelude::*;
use crate::lune::util::TableBuilder; use crate::lune::util::TableBuilder;
mod captures;
mod matches;
mod regex;
use self::regex::LuaRegex;
pub fn create(lua: &Lua) -> LuaResult<LuaTable> { pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?.build_readonly() TableBuilder::new(lua)?
.with_function("new", new_regex)?
.build_readonly()
}
fn new_regex(_: &Lua, pattern: String) -> LuaResult<LuaRegex> {
LuaRegex::new(pattern)
} }

View file

@ -0,0 +1,66 @@
use std::sync::Arc;
use mlua::prelude::*;
use regex::Regex;
use super::{captures::LuaCaptures, matches::LuaMatch};
pub struct LuaRegex {
inner: Regex,
}
impl LuaRegex {
pub fn new(pattern: String) -> LuaResult<Self> {
Regex::new(&pattern)
.map(|inner| Self { inner })
.map_err(LuaError::external)
}
}
impl LuaUserData for LuaRegex {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("isMatch", |_, this, text: String| {
Ok(this.inner.is_match(&text))
});
methods.add_method("find", |_, this, text: String| {
let arc = Arc::new(text);
Ok(this
.inner
.find(&arc)
.map(|m| LuaMatch::new(Arc::clone(&arc), m)))
});
methods.add_method("captures", |_, this, text: String| {
Ok(LuaCaptures::new(&this.inner, text))
});
methods.add_method("split", |_, this, text: String| {
Ok(this
.inner
.split(&text)
.map(|s| s.to_string())
.collect::<Vec<_>>())
});
// TODO: Determine whether it's desirable and / or feasible to support
// using a function or table for `replace` like in the lua string library
methods.add_method(
"replace",
|_, this, (haystack, replacer): (String, String)| {
Ok(this.inner.replace(&haystack, replacer).to_string())
},
);
methods.add_method(
"replaceAll",
|_, this, (haystack, replacer): (String, String)| {
Ok(this.inner.replace_all(&haystack, replacer).to_string())
},
);
methods.add_meta_method(LuaMetaMethod::Type, |_, _, ()| Ok("Regex"));
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
Ok(format!("Regex({})", this.inner.as_str()))
});
}
}