diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js index 6f91ec3900..535bfa7268 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js +++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.exec.js @@ -119,3 +119,11 @@ test("named capture group with two '?' qualifiers", () => { expect(res[1]).toBeUndefined(); expect(res.groups.foo).toBeUndefined(); }); + +// #6042 +test("non-greedy brace quantifier", () => { + let res = /a[a-z]{2,4}?/.exec("abcdefghi"); + + expect(res.length).toBe(1); + expect(res[0]).toBe("abc"); +}); diff --git a/Userland/Libraries/LibRegex/RegexByteCode.h b/Userland/Libraries/LibRegex/RegexByteCode.h index da9706fae2..2b1419a003 100644 --- a/Userland/Libraries/LibRegex/RegexByteCode.h +++ b/Userland/Libraries/LibRegex/RegexByteCode.h @@ -362,26 +362,28 @@ public: // LABEL _END = alterantive_bytecode.size } - void insert_bytecode_repetition_min_max(ByteCode& bytecode_to_repeat, size_t minimum, Optional maximum) + void insert_bytecode_repetition_min_max(ByteCode& bytecode_to_repeat, size_t minimum, Optional maximum, bool greedy = true) { ByteCode new_bytecode; new_bytecode.insert_bytecode_repetition_n(bytecode_to_repeat, minimum); if (maximum.has_value()) { + auto jump_kind = static_cast(greedy ? OpCodeId::ForkStay : OpCodeId::ForkJump); if (maximum.value() > minimum) { auto diff = maximum.value() - minimum; - new_bytecode.empend(static_cast(OpCodeId::ForkStay)); + new_bytecode.empend(jump_kind); new_bytecode.empend(diff * (bytecode_to_repeat.size() + 2)); // Jump to the _END label for (size_t i = 0; i < diff; ++i) { new_bytecode.append(bytecode_to_repeat); - new_bytecode.empend(static_cast(OpCodeId::ForkStay)); + new_bytecode.empend(jump_kind); new_bytecode.empend((diff - i - 1) * (bytecode_to_repeat.size() + 2)); // Jump to the _END label } } } else { // no maximum value set, repeat finding if possible - new_bytecode.empend(static_cast(OpCodeId::ForkJump)); + auto jump_kind = static_cast(greedy ? OpCodeId::ForkJump : OpCodeId::ForkStay); + new_bytecode.empend(jump_kind); new_bytecode.empend(-bytecode_to_repeat.size() - 2); // Jump to the last iteration } diff --git a/Userland/Libraries/LibRegex/RegexParser.cpp b/Userland/Libraries/LibRegex/RegexParser.cpp index 97f201cae0..54400f958b 100644 --- a/Userland/Libraries/LibRegex/RegexParser.cpp +++ b/Userland/Libraries/LibRegex/RegexParser.cpp @@ -968,10 +968,6 @@ bool ECMA262Parser::parse_quantifier(ByteCode& stack, size_t& match_length_minim } if (match(TokenType::Questionmark)) { - if (repetition_mark == Repetition::Explicit) { - set_error(Error::InvalidRepetitionMarker); - return false; - } consume(); ungreedy = true; } @@ -990,7 +986,7 @@ bool ECMA262Parser::parse_quantifier(ByteCode& stack, size_t& match_length_minim match_length_minimum = 0; break; case Repetition::Explicit: - new_bytecode.insert_bytecode_repetition_min_max(stack, repeat_min.value(), repeat_max); + new_bytecode.insert_bytecode_repetition_min_max(stack, repeat_min.value(), repeat_max, !ungreedy); match_length_minimum *= repeat_min.value(); break; case Repetition::None: