From 182abc9eb7937fb2265de884a6e2cf1de0aee2a0 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Fri, 21 Feb 2025 01:12:21 +0500 Subject: [PATCH 1/4] feat: add support for RegExp.leftContext RegExp.rightContext --- quickjs.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/quickjs.c b/quickjs.c index 9d39e9008..a326f72e1 100644 --- a/quickjs.c +++ b/quickjs.c @@ -409,6 +409,12 @@ struct JSContext { JSValue global_obj; /* global object */ JSValue global_var_obj; /* contains the global let/const definitions */ + JSValue regexp_left_ctx; // RegExp.leftContext + JSValue regexp_right_ctx; // RegExp.rightContext + JSValue regexp_last_match_str; + int64_t regexp_last_match_start; + int64_t regexp_last_match_end; + double time_origin; uint64_t random_state; @@ -2240,6 +2246,11 @@ JSContext *JS_NewContextRaw(JSRuntime *rt) ctx->error_back_trace = JS_UNDEFINED; ctx->error_prepare_stack = JS_UNDEFINED; ctx->error_stack_trace_limit = js_int32(10); + ctx->regexp_left_ctx = JS_UNDEFINED; + ctx->regexp_right_ctx = JS_UNDEFINED; + ctx->regexp_last_match_str = JS_UNDEFINED; + ctx->regexp_last_match_start = -1; + ctx->regexp_last_match_end = -1; init_list_head(&ctx->loaded_modules); JS_AddIntrinsicBasicObjects(ctx); @@ -2440,6 +2451,9 @@ void JS_FreeContext(JSContext *ctx) JS_FreeValue(ctx, ctx->regexp_ctor); JS_FreeValue(ctx, ctx->function_ctor); JS_FreeValue(ctx, ctx->function_proto); + JS_FreeValue(ctx, ctx->regexp_left_ctx); + JS_FreeValue(ctx, ctx->regexp_right_ctx); + JS_FreeValue(ctx, ctx->regexp_last_match_str); js_free_shape_null(ctx->rt, ctx->array_shape); @@ -43821,6 +43835,24 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValue this_val, return string_buffer_end(b); } +static JSValue js_regexp_get_leftContext(JSContext *ctx, JSValue this_val) { + if (ctx->regexp_last_match_start >= 0) { + JS_FreeValue(ctx, ctx->regexp_left_ctx); + ctx->regexp_left_ctx = js_sub_string(ctx, JS_VALUE_GET_STRING(ctx->regexp_last_match_str), 0, ctx->regexp_last_match_start); + ctx->regexp_last_match_start = -1; // Reset to avoid recomputation + } + return JS_DupValue(ctx, ctx->regexp_left_ctx); +} + +static JSValue js_regexp_get_rightContext(JSContext *ctx, JSValue this_val) { + if (ctx->regexp_last_match_end >= 0) { + JS_FreeValue(ctx, ctx->regexp_right_ctx); + ctx->regexp_right_ctx = js_sub_string(ctx, JS_VALUE_GET_STRING(ctx->regexp_last_match_str), ctx->regexp_last_match_end, JS_VALUE_GET_STRING(ctx->regexp_last_match_str)->len); + ctx->regexp_last_match_end = -1; // Reset to avoid recomputation + } + return JS_DupValue(ctx, ctx->regexp_right_ctx); +} + static JSValue js_regexp_exec(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { @@ -44007,6 +44039,11 @@ static JSValue js_regexp_exec(JSContext *ctx, JSValue this_val, goto fail; } } + + JS_FreeValue(ctx, ctx->regexp_last_match_str); + ctx->regexp_last_match_str = JS_DupValue(ctx, argv[0]); + ctx->regexp_last_match_start = (capture[0] - str_buf) >> shift; + ctx->regexp_last_match_end = (capture[1] - str_buf) >> shift; } ret = obj; obj = JS_UNDEFINED; @@ -44850,6 +44887,8 @@ static JSValue js_regexp_Symbol_split(JSContext *ctx, JSValue this_val, static const JSCFunctionListEntry js_regexp_funcs[] = { JS_CFUNC_DEF("escape", 1, js_regexp_escape ), + JS_CGETSET_DEF("leftContext", js_regexp_get_leftContext, NULL), + JS_CGETSET_DEF("rightContext", js_regexp_get_rightContext, NULL), JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), }; From c6451346d51ff492412bfe0e20620050e64cd7fd Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Sun, 2 Mar 2025 09:45:48 +0500 Subject: [PATCH 2/4] fix: apply suggestions --- quickjs.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/quickjs.c b/quickjs.c index a326f72e1..60455e73f 100644 --- a/quickjs.c +++ b/quickjs.c @@ -2382,6 +2382,10 @@ static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, JS_MarkValue(rt, ctx->function_ctor, mark_func); JS_MarkValue(rt, ctx->function_proto, mark_func); + JS_MarkValue(rt, ctx->regexp_last_match_str, mark_func); + JS_MarkValue(rt, ctx->regexp_left_ctx, mark_func); + JS_MarkValue(rt, ctx->regexp_right_ctx, mark_func); + if (ctx->array_shape) mark_func(rt, &ctx->array_shape->header); } @@ -43836,18 +43840,25 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValue this_val, } static JSValue js_regexp_get_leftContext(JSContext *ctx, JSValue this_val) { - if (ctx->regexp_last_match_start >= 0) { + if (ctx->regexp_last_match_start >= 0 && JS_IsString(ctx->regexp_last_match_str)) { + JSString* p; JS_FreeValue(ctx, ctx->regexp_left_ctx); - ctx->regexp_left_ctx = js_sub_string(ctx, JS_VALUE_GET_STRING(ctx->regexp_last_match_str), 0, ctx->regexp_last_match_start); + ctx->regexp_left_ctx = JS_UNDEFINED; + p = JS_VALUE_GET_STRING(ctx->regexp_last_match_str); + ctx->regexp_left_ctx = js_sub_string(ctx, p, 0, ctx->regexp_last_match_start); ctx->regexp_last_match_start = -1; // Reset to avoid recomputation } return JS_DupValue(ctx, ctx->regexp_left_ctx); } static JSValue js_regexp_get_rightContext(JSContext *ctx, JSValue this_val) { - if (ctx->regexp_last_match_end >= 0) { + if (ctx->regexp_last_match_end >= 0 && JS_IsString(ctx->regexp_last_match_str)) { + JSString* p; JS_FreeValue(ctx, ctx->regexp_right_ctx); - ctx->regexp_right_ctx = js_sub_string(ctx, JS_VALUE_GET_STRING(ctx->regexp_last_match_str), ctx->regexp_last_match_end, JS_VALUE_GET_STRING(ctx->regexp_last_match_str)->len); + ctx->regexp_right_ctx = JS_UNDEFINED; + + p = JS_VALUE_GET_STRING(ctx->regexp_last_match_str); + ctx->regexp_right_ctx = js_sub_string(ctx, p, ctx->regexp_last_match_end, p->len); ctx->regexp_last_match_end = -1; // Reset to avoid recomputation } return JS_DupValue(ctx, ctx->regexp_right_ctx); From c2c2598bd5322308b2e19df6563c85149f6c6248 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Sun, 2 Mar 2025 09:46:09 +0500 Subject: [PATCH 3/4] Enable legacy regexp tests --- test262.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test262.conf b/test262.conf index 6cde7b8f3..cacd6cd7c 100644 --- a/test262.conf +++ b/test262.conf @@ -143,7 +143,7 @@ iterator-sequencing=skip json-modules=skip json-parse-with-source=skip json-superset -legacy-regexp=skip +legacy-regexp let logical-assignment-operators Map From 0471304cd6e6d7a2cd204be9bae96c42687bd8b1 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Sun, 2 Mar 2025 09:51:37 +0500 Subject: [PATCH 4/4] Add legacy_regexp tests --- tests/legacy_regexp.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/legacy_regexp.js diff --git a/tests/legacy_regexp.js b/tests/legacy_regexp.js new file mode 100644 index 000000000..2ae1474d9 --- /dev/null +++ b/tests/legacy_regexp.js @@ -0,0 +1,8 @@ +import { assert } from "./assert.js"; + +const testString = "Hello, world!"; +const regex = /world/; +regex.exec(testString); + +assert(RegExp.leftContext === "Hello, ", true); +assert(RegExp.rightContext === "!", true);