1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:47:36 +00:00

LibJS: Implement `RegExpPrototype::exec()'

This implements *only* the builtin exec() function.
This commit is contained in:
AnotherTest 2020-11-22 14:18:49 +03:30 committed by Andreas Kling
parent 8ba273a2f3
commit 210a3db44d
4 changed files with 114 additions and 0 deletions

View file

@ -91,6 +91,7 @@ namespace JS {
P(enumerable) \
P(error) \
P(every) \
P(exec) \
P(exp) \
P(expm1) \
P(fill) \
@ -126,12 +127,15 @@ namespace JS {
P(getUTCSeconds) \
P(global) \
P(globalThis) \
P(groups) \
P(has) \
P(hasOwnProperty) \
P(ignoreCase) \
P(includes) \
P(index) \
P(indexOf) \
P(info) \
P(input) \
P(is) \
P(isArray) \
P(isExtensible) \

View file

@ -25,6 +25,7 @@
*/
#include <AK/Function.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/RegExpObject.h>
@ -44,6 +45,7 @@ void RegExpPrototype::initialize(GlobalObject& global_object)
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(vm.names.toString, to_string, 0, attr);
define_native_function(vm.names.test, test, 1, attr);
define_native_function(vm.names.exec, exec, 1, attr);
u8 readable_attr = Attribute::Configurable;
define_native_property(vm.names.dotAll, dot_all, nullptr, readable_attr);
@ -170,6 +172,55 @@ RegexResult RegExpPrototype::do_match(const Regex<ECMA262>& re, const StringView
return result;
}
JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::exec)
{
// FIXME: This should try using dynamic properties for 'lastIndex',
// and internal slots [[RegExpMatcher]], [[OriginalFlags]], etc.
auto regexp_object = regexp_object_from(vm, global_object);
if (!regexp_object)
return {};
auto str = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
StringView str_to_match = str;
// RegExps without "global" and "sticky" always start at offset 0.
if (!regexp_object->regex().options().has_flag_set((ECMAScriptFlags)regex::AllFlags::Internal_Stateful))
regexp_object->regex().start_offset = 0;
auto result = do_match(regexp_object->regex(), str_to_match);
if (!result.success)
return js_null();
auto& match = result.matches[0];
// FIXME: Do code point index correction if the Unicode flag is set.
auto* array = Array::create(global_object);
array->indexed_properties().set_array_like_size(result.n_capture_groups + 1);
array->define_property(vm.names.index, Value((i32)match.column));
array->define_property(vm.names.input, js_string(vm, str));
array->indexed_properties().put(array, 0, js_string(vm, match.view.to_string()));
for (size_t i = 0; i < result.n_capture_groups; ++i) {
auto& capture = result.capture_group_matches[0][i];
array->indexed_properties().put(array, i + 1, js_string(vm, capture.view.to_string()));
}
Value groups = js_undefined();
if (result.n_named_capture_groups > 0) {
auto groups_object = create_empty(global_object);
for (auto& entry : result.named_capture_group_matches[0])
groups_object->define_property(entry.key, js_string(vm, entry.value.view.to_string()));
groups = move(groups_object);
}
array->define_property(vm.names.groups, groups);
return array;
}
JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::test)
{
// FIXME: This should try using dynamic properties for 'exec' first,

View file

@ -50,6 +50,7 @@ private:
JS_DECLARE_NATIVE_GETTER(sticky);
JS_DECLARE_NATIVE_GETTER(unicode);
JS_DECLARE_NATIVE_FUNCTION(exec);
JS_DECLARE_NATIVE_FUNCTION(test);
JS_DECLARE_NATIVE_FUNCTION(to_string);
};

View file

@ -0,0 +1,58 @@
test("basic functionality", () => {
let re = /foo/;
expect(re.exec.length).toBe(1);
let res = re.exec("foo");
expect(res.length).toBe(1);
expect(res[0]).toBe("foo");
expect(res.groups).toBe(undefined);
expect(res.index).toBe(0);
});
test("basic unnamed captures", () => {
let re = /f(o.*)/;
let res = re.exec("fooooo");
expect(res.length).toBe(2);
expect(res[0]).toBe("fooooo");
expect(res[1]).toBe("ooooo");
expect(res.groups).toBe(undefined);
expect(res.index).toBe(0);
});
test("basic named captures", () => {
let re = /f(?<os>o.*)/;
let res = re.exec("fooooo");
expect(res.length).toBe(1);
expect(res.index).toBe(0);
expect(res[0]).toBe("fooooo");
expect(res.groups).not.toBe(undefined);
expect(res.groups.os).toBe("ooooo");
});
test("basic index", () => {
let re = /foo/;
let res = re.exec("abcfoo");
expect(res.length).toBe(1);
expect(res.index).toBe(3);
expect(res[0]).toBe("foo");
});
test("basic index with global and initial offset", () => {
let re = /foo/g;
re.lastIndex = 2;
let res = re.exec("abcfoo");
expect(res.length).toBe(1);
expect(res.index).toBe(3);
expect(res[0]).toBe("foo");
});
test("not matching", () => {
let re = /foo/;
let res = re.exec("bar");
expect(res).toBe(null);
});