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

LibJS: Implement (mostly) String.prototype.match

JavaScript has a couple of different ways to run a regular expression
on a string. This adds support for one more. :^)
This commit is contained in:
Andreas Kling 2021-03-14 11:03:11 +01:00
parent 2c24c0e451
commit 1db943e146
7 changed files with 91 additions and 1 deletions

View file

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

View file

@ -37,6 +37,8 @@ struct Flags {
namespace JS {
RegExpObject* regexp_create(GlobalObject&, Value pattern, Value flags);
class RegExpObject : public Object {
JS_OBJECT(RegExpObject, Object);

View file

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

View file

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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
* All rights reserved.
*
@ -33,6 +33,7 @@
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/StringIterator.h>
#include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/StringPrototype.h>
@ -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<TypeError>(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);
}
}

View file

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

View file

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