use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::Vm;
fn vm() -> Vm {
Vm::new(LuaVersion::Lua55)
}
fn eval_str(vm: &mut Vm, src: &str) -> String {
let cl = vm.load(src.as_bytes(), b"=cv_pattern").expect("load");
let r = vm.call_value(Value::Closure(cl), &[]).expect("eval");
match r.into_iter().next().unwrap_or(Value::Nil) {
Value::Str(s) => String::from_utf8_lossy(s.as_bytes()).to_string(),
other => panic!("expected string, got {other:?}"),
}
}
fn eval_int(vm: &mut Vm, src: &str) -> i64 {
let cl = vm.load(src.as_bytes(), b"=cv_pattern").expect("load");
let r = vm.call_value(Value::Closure(cl), &[]).expect("eval");
match r.into_iter().next().unwrap_or(Value::Nil) {
Value::Int(n) => n,
Value::Float(f) => f as i64,
other => panic!("expected int, got {other:?}"),
}
}
#[test]
fn pattern_class_shortcuts_match_correctly() {
let mut v = vm();
assert_eq!(
eval_str(&mut v, r#"return string.match("Hello", "%a+")"#),
"Hello"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("abc123", "%d+")"#),
"123"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("abc_123", "%w+")"#),
"abc"
);
assert_eq!(eval_str(&mut v, r#"return string.match("a b", "%s")"#), " ");
assert_eq!(
eval_str(&mut v, r#"return string.match("AbC", "%l+")"#),
"b"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("AbC", "%u+")"#),
"A"
);
assert_eq!(eval_str(&mut v, r#"return string.match("a,b", "%p")"#), ",");
}
#[test]
fn pattern_quantifier_variants() {
let mut v = vm();
assert_eq!(eval_int(&mut v, r#"return #string.match("ab", "x*")"#), 0);
assert_eq!(
eval_str(&mut v, r#"return string.match("aaa", "a+")"#),
"aaa"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("ac", "ab?c")"#),
"ac"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("abc", "ab?c")"#),
"abc"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("<a><b>", "<.->")"#),
"<a>"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("<a><b>", "<.*>")"#),
"<a><b>"
);
}
#[test]
fn pattern_anchors_caret_dollar() {
let mut v = vm();
assert_eq!(
eval_str(&mut v, r#"return string.match("hello", "^h")"#),
"h"
);
assert_eq!(
eval_str(&mut v, r#"return tostring(string.match("hello", "^e"))"#),
"nil"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("hello", "o$")"#),
"o"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("abc", "^abc$")"#),
"abc"
);
assert_eq!(
eval_str(&mut v, r#"return tostring(string.match("abcd", "^abc$"))"#),
"nil"
);
}
#[test]
fn pattern_bracket_sets() {
let mut v = vm();
assert_eq!(
eval_str(&mut v, r#"return string.match("xyz123", "[0-9]+")"#),
"123"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("abc", "[abc]+")"#),
"abc"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("hello", "[^aeiou]+")"#),
"h"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("xyz_abc_xyz", "[a-f]+")"#),
"abc"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("12X34", "[0-9X]+")"#),
"12X34"
);
}
#[test]
fn pattern_multi_capture_groups() {
let mut v = vm();
let cl = v
.load(
br#"local a, b, c = string.match("abc123xyz", "(%a+)(%d+)(%a+)")
return a .. "|" .. b .. "|" .. c"#,
b"=cv_pattern",
)
.expect("load");
let r = v.call_value(Value::Closure(cl), &[]).expect("eval");
match r.into_iter().next().unwrap() {
Value::Str(s) => assert_eq!(s.as_bytes(), b"abc|123|xyz"),
other => panic!("expected string, got {other:?}"),
}
}
#[test]
fn pattern_position_capture() {
let mut v = vm();
assert_eq!(
eval_int(&mut v, r#"return (string.match("abc", "()b"))"#),
2
);
}
#[test]
fn pattern_escape_special_chars() {
let mut v = vm();
assert_eq!(
eval_str(&mut v, r#"return string.match("a.b", "a%.b")"#),
"a.b"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("a(b)c", "a%(b%)c")"#),
"a(b)c"
);
assert_eq!(
eval_str(&mut v, r#"return string.match("100%", "%d+%%")"#),
"100%"
);
}
#[test]
fn pattern_gmatch_iterator() {
let mut v = vm();
let cl = v
.load(
br#"local out = {}
for w in string.gmatch("apple,banana,cherry", "[^,]+") do
out[#out+1] = w
end
return out[1] .. "|" .. out[2] .. "|" .. out[3] .. "|" .. #out"#,
b"=cv_pattern",
)
.expect("load");
let r = v.call_value(Value::Closure(cl), &[]).expect("eval");
match r.into_iter().next().unwrap() {
Value::Str(s) => assert_eq!(s.as_bytes(), b"apple|banana|cherry|3"),
other => panic!("expected string, got {other:?}"),
}
}
#[test]
fn pattern_gmatch_yields_captures() {
let mut v = vm();
let cl = v
.load(
br#"local out = ""
for k, v in string.gmatch("k1=v1;k2=v2", "(%w+)=(%w+)") do
out = out .. k .. ":" .. v .. ";"
end
return out"#,
b"=cv_pattern",
)
.expect("load");
let r = v.call_value(Value::Closure(cl), &[]).expect("eval");
match r.into_iter().next().unwrap() {
Value::Str(s) => assert_eq!(s.as_bytes(), b"k1:v1;k2:v2;"),
other => panic!("expected string, got {other:?}"),
}
}
#[test]
fn pattern_string_find_plain_mode() {
let mut v = vm();
assert_eq!(
eval_str(
&mut v,
r#"return tostring(string.find("abc123", "%d+", 1, true))"#
),
"nil"
);
assert_eq!(
eval_int(&mut v, r#"return (string.find("abc123", "123", 1, true))"#),
4
);
assert_eq!(
eval_int(
&mut v,
r#"return (string.find("hello hello", "hello", 2, true))"#
),
7
);
}
#[test]
fn pattern_gsub_count_return() {
let mut v = vm();
let cl = v
.load(
br#"local s, n = string.gsub("a-b-c-d", "-", "/")
return s .. ":" .. n"#,
b"=cv_pattern",
)
.expect("load");
let r = v.call_value(Value::Closure(cl), &[]).expect("eval");
match r.into_iter().next().unwrap() {
Value::Str(s) => assert_eq!(s.as_bytes(), b"a/b/c/d:3"),
other => panic!("expected string, got {other:?}"),
}
}
#[test]
fn pattern_gsub_max_replacements() {
let mut v = vm();
let cl = v
.load(
br#"local s, n = string.gsub("a-b-c-d-e", "-", "/", 2)
return s .. ":" .. n"#,
b"=cv_pattern",
)
.expect("load");
let r = v.call_value(Value::Closure(cl), &[]).expect("eval");
match r.into_iter().next().unwrap() {
Value::Str(s) => assert_eq!(s.as_bytes(), b"a/b/c-d-e:2"),
other => panic!("expected string, got {other:?}"),
}
}