From 210a3db44d773d92f6f9471acce9d1310218fa53 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 22 Nov 2020 14:18:49 +0330 Subject: [PATCH] LibJS: Implement `RegExpPrototype::exec()' This implements *only* the builtin exec() function. --- Libraries/LibJS/Runtime/CommonPropertyNames.h | 4 ++ Libraries/LibJS/Runtime/RegExpPrototype.cpp | 51 ++++++++++++++++ Libraries/LibJS/Runtime/RegExpPrototype.h | 1 + .../builtins/RegExp/RegExp.prototype.exec.js | 58 +++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js diff --git a/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Libraries/LibJS/Runtime/CommonPropertyNames.h index 5b6ed4b613..780a48e757 100644 --- a/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/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) \ diff --git a/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Libraries/LibJS/Runtime/RegExpPrototype.cpp index bbd21ed53c..165bde164d 100644 --- a/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -25,6 +25,7 @@ */ #include +#include #include #include #include @@ -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& 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, diff --git a/Libraries/LibJS/Runtime/RegExpPrototype.h b/Libraries/LibJS/Runtime/RegExpPrototype.h index fd721fd06d..4358a0b308 100644 --- a/Libraries/LibJS/Runtime/RegExpPrototype.h +++ b/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); }; diff --git a/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js b/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js new file mode 100644 index 0000000000..7fcbf3582a --- /dev/null +++ b/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(?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); +});