diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp index 2bcd12bf05..0ba76e530a 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp @@ -168,4 +168,30 @@ JS_DEFINE_NATIVE_SETTER(RegExpObject::set_last_index) regexp_object->regex().start_offset = index; } +RegExpObject* regexp_create(GlobalObject& global_object, Value pattern, Value flags) +{ + // https://tc39.es/ecma262/#sec-regexpcreate + String p; + if (pattern.is_undefined()) { + p = String::empty(); + } else { + p = pattern.to_string(global_object); + if (p.is_null()) + return nullptr; + } + String f; + if (flags.is_empty()) { + f = String::empty(); + } else { + f = flags.to_string(global_object); + if (f.is_null()) + return nullptr; + } + // FIXME: This is awkward: the RegExpObject C++ constructor may throw a VM exception. + auto* obj = RegExpObject::create(global_object, move(p), move(f)); + if (global_object.vm().exception()) + return nullptr; + return obj; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.h b/Userland/Libraries/LibJS/Runtime/RegExpObject.h index 7be6b96069..1f2a2ed1d6 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpObject.h +++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.h @@ -37,6 +37,8 @@ struct Flags { namespace JS { +RegExpObject* regexp_create(GlobalObject&, Value pattern, Value flags); + class RegExpObject : public Object { JS_OBJECT(RegExpObject, Object); diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp index 8c7d5b1a74..bb74ee8550 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -49,6 +49,8 @@ void RegExpPrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.test, test, 1, attr); define_native_function(vm.names.exec, exec, 1, attr); + define_native_function(vm.well_known_symbol_match(), symbol_match, 1, attr); + u8 readable_attr = Attribute::Configurable; define_native_property(vm.names.flags, flags, {}, readable_attr); define_native_property(vm.names.source, source, {}, readable_attr); @@ -254,4 +256,26 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::to_string) return js_string(vm, String::formatted("/{}/{}", pattern, flags)); } +JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_match) +{ + // https://tc39.es/ecma262/#sec-regexp.prototype-@@match + auto* rx = this_object_from(vm, global_object); + if (!rx) + return {}; + auto string = vm.argument(0); + auto s = string.to_string(global_object); + auto global_value = rx->get(vm.names.global); + if (global_value.is_empty()) + return {}; + bool global = global_value.to_boolean(); + auto* exec = get_method(global_object, rx, vm.names.exec); + if (!exec) + return {}; + if (!global) + return vm.call(*exec, rx, string); + + // FIXME: This should exec the RegExp repeatedly while updating "lastIndex" + return vm.call(*exec, rx, string); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h index 372c64a41c..6ab54a7ca6 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h @@ -47,6 +47,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(exec); JS_DECLARE_NATIVE_FUNCTION(test); JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(symbol_match); #define __JS_ENUMERATE(_, flag_name, ...) \ JS_DECLARE_NATIVE_GETTER(flag_name); diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index fa04e21a2c..98e5365954 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020-2021, Linus Groh * All rights reserved. * @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,7 @@ void StringPrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.split, split, 2, attr); define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr); define_native_function(vm.names.at, at, 1, attr); + define_native_function(vm.names.match, match, 1, attr); define_native_function(vm.well_known_symbol_iterator(), symbol_iterator, 0, attr); } @@ -651,4 +653,32 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator) return StringIterator::create(global_object, string); } +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match) +{ + // https://tc39.es/ecma262/#sec-string.prototype.match + auto this_object = vm.this_value(global_object); + if (this_object.is_nullish()) { + vm.throw_exception(global_object, ErrorType::ToObjectNullOrUndefined); + return {}; + } + auto regexp = vm.argument(0); + if (!regexp.is_nullish()) { + if (auto* matcher = get_method(global_object, regexp, vm.well_known_symbol_match())) + return vm.call(*matcher, regexp, this_object); + } + auto s = this_object.to_primitive_string(global_object); + if (!s) + return {}; + auto regexp_string = regexp.to_string(global_object); + if (regexp_string.is_null()) + return {}; + auto rx = regexp_create(global_object, regexp, js_undefined()); + if (!rx) + return {}; + auto* matcher = get_method(global_object, rx, vm.well_known_symbol_match()); + if (!matcher) + return {}; + return vm.call(*matcher, rx, this_object); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.h b/Userland/Libraries/LibJS/Runtime/StringPrototype.h index d7be6e1f16..d764b8fd6b 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.h @@ -63,6 +63,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(split); JS_DECLARE_NATIVE_FUNCTION(last_index_of); JS_DECLARE_NATIVE_FUNCTION(at); + JS_DECLARE_NATIVE_FUNCTION(match); JS_DECLARE_NATIVE_FUNCTION(symbol_iterator); }; diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.match.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.match.js new file mode 100644 index 0000000000..7d5e91dfb9 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.match.js @@ -0,0 +1,6 @@ +test("basic functionality", () => { + expect(String.prototype.match).toHaveLength(1); + + expect("hello friends".match(/hello/)).not.toBeNull(); + expect("hello friends".match(/enemies/)).toBeNull(); +});