Pārlūkot izejas kodu

LibJS: Implement `RegExpPrototype::exec()'

This implements *only* the builtin exec() function.
AnotherTest 4 gadi atpakaļ
vecāks
revīzija
210a3db44d

+ 4 - 0
Libraries/LibJS/Runtime/CommonPropertyNames.h

@@ -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)                          \

+ 51 - 0
Libraries/LibJS/Runtime/RegExpPrototype.cpp

@@ -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,

+ 1 - 0
Libraries/LibJS/Runtime/RegExpPrototype.h

@@ -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);
 };

+ 58 - 0
Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js

@@ -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);
+});