Explorar el Código

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. :^)
Andreas Kling hace 4 años
padre
commit
1db943e146

+ 26 - 0
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;
+}
+
 }

+ 2 - 0
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);
 

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

+ 1 - 0
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);

+ 31 - 1
Userland/Libraries/LibJS/Runtime/StringPrototype.cpp

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

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

+ 6 - 0
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();
+});