Skip to content

Commit

Permalink
add RegExp lookbehind assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed Feb 13, 2018
1 parent ff7de09 commit eb75208
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 21 deletions.
3 changes: 1 addition & 2 deletions bin/run_test262.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ const unsupportedFeatures = [
"class-fields",
"class-fields-private",
"class-fields-public",
"optional-catch-binding",
"regexp-lookbehind"
"optional-catch-binding"
];

run(
Expand Down
38 changes: 19 additions & 19 deletions src/regexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export class RegExpValidator {
this.pos = 0
this.lastIntValue = 0
this.lastStringValue = ""
this.lastAssertionIsQuantifiable = false
this.numCapturingParens = 0
this.maxBackReference = 0
this.groupNames = []
Expand Down Expand Up @@ -211,6 +212,9 @@ export class RegExpValidator {
// https://www.ecma-international.org/ecma-262/8.0/#prod-Pattern
pattern() {
this.pos = 0
this.lastIntValue = 0
this.lastStringValue = ""
this.lastAssertionIsQuantifiable = false
this.numCapturingParens = 0
this.maxBackReference = 0
this.groupNames.length = 0
Expand Down Expand Up @@ -261,20 +265,16 @@ export class RegExpValidator {

// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-Term
eatTerm() {
const start = this.pos

if (this.eatQuantifiableAssertion()) {
if (this.eatQuantifier()) {
if (this.eatAssertion()) {
// Handle `QuantifiableAssertion Quantifier` alternative.
// `this.lastAssertionIsQuantifiable` is true if the last eaten Assertion
// is a QuantifiableAssertion.
if (this.lastAssertionIsQuantifiable && this.eatQuantifier()) {
// Make the same message as V8.
if (this.switchU) {
this.raise("Invalid quantifier")
}
return true
}
this.pos = start
}

if (this.eatAssertion()) {
return true
}

Expand All @@ -288,11 +288,12 @@ export class RegExpValidator {

// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-Assertion
eatAssertion() {
this.lastAssertionIsQuantifiable = false
return (
this.eat(CIRCUMFLEX_ACCENT) ||
this.eat(DOLLAR_SIGN) ||
this._eatWordBoundary() ||
this._eatLookaheadAssertion()
this._eatLookaheadOrLookbehindAssertion()
)
}
_eatWordBoundary() {
Expand All @@ -305,26 +306,25 @@ export class RegExpValidator {
}
return false
}
_eatLookaheadAssertion() {
_eatLookaheadOrLookbehindAssertion() {
const start = this.pos
if (this.eat(LEFT_PARENTHESIS)) {
if (this.eat(QUESTION_MARK) && (this.eat(EQUALS_SIGN) || this.eat(EXCLAMATION_MARK))) {
if (this.eat(LEFT_PARENTHESIS) && this.eat(QUESTION_MARK)) {
if (this.ecmaVersion >= 9) {
this.eat(LESS_THAN_SIGN)
}
if (this.eat(EQUALS_SIGN) || this.eat(EXCLAMATION_MARK)) {
this.disjunction()
if (!this.eat(RIGHT_PARENTHESIS)) {
this.raise("Unterminated group")
}
this.lastAssertionIsQuantifiable = true
return true
}
this.pos = start
}
this.pos = start
return false
}

// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-QuantifiableAssertion
eatQuantifiableAssertion() {
return this._eatLookaheadAssertion()
}

// https://www.ecma-international.org/ecma-262/8.0/#prod-Quantifier
eatQuantifier(noError = false) {
if (this.eatQuantifierPrefix(noError)) {
Expand Down
48 changes: 48 additions & 0 deletions test/tests-regexp-2018.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,51 @@ testFail("/\\p{General_Category=Hiragana}/u", "Invalid regular expression: /\\p{
test("/\\p{Script=Hiragana}/u", {}, { ecmaVersion: 2018 })
testFail("/[\\p{Script=Hiragana}-\\p{Script=Katakana}]/u", "Invalid regular expression: /[\\p{Script=Hiragana}-\\p{Script=Katakana}]/: Invalid character class (1:1)", { ecmaVersion: 2018 })
test("/[\\p{Script=Hiragana}\\-\\p{Script=Katakana}]/u", {}, { ecmaVersion: 2018 })

//------------------------------------------------------------------------------
// Lookbehind assertions
//------------------------------------------------------------------------------

testFail("/(?<a)/", "Invalid regular expression: /(?<a)/: Invalid group (1:1)", { ecmaVersion: 2017 })
testFail("/(?<a)/u", "Invalid regular expression: /(?<a)/: Invalid group (1:1)", { ecmaVersion: 2017 })
testFail("/(?<a)/", "Invalid regular expression: /(?<a)/: Invalid capture group name (1:1)", { ecmaVersion: 2018 })
testFail("/(?<a)/u", "Invalid regular expression: /(?<a)/: Invalid capture group name (1:1)", { ecmaVersion: 2018 })
testFail("/(?<=a)/", "Invalid regular expression: /(?<=a)/: Invalid group (1:1)", { ecmaVersion: 2017 })
testFail("/(?<=a)/u", "Invalid regular expression: /(?<=a)/: Invalid group (1:1)", { ecmaVersion: 2017 })
test("/(?<=a)/", {}, { ecmaVersion: 2018 })
test("/(?<=a)/u", {}, { ecmaVersion: 2018 })
testFail("/(?<!a)/", "Invalid regular expression: /(?<!a)/: Invalid group (1:1)", { ecmaVersion: 2017 })
testFail("/(?<!a)/u", "Invalid regular expression: /(?<!a)/: Invalid group (1:1)", { ecmaVersion: 2017 })
test("/(?<!a)/", {}, { ecmaVersion: 2018 })
test("/(?<!a)/u", {}, { ecmaVersion: 2018 })

test("/(?<=a)?/", {}, { ecmaVersion: 2018 })
testFail("/(?<=a)?/u", "Invalid regular expression: /(?<=a)?/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })
test("/(?<=a)+/", {}, { ecmaVersion: 2018 })
testFail("/(?<=a)+/u", "Invalid regular expression: /(?<=a)+/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })
test("/(?<=a)*/", {}, { ecmaVersion: 2018 })
testFail("/(?<=a)*/u", "Invalid regular expression: /(?<=a)*/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })
test("/(?<=a){1}/", {}, { ecmaVersion: 2018 })
testFail("/(?<=a){1}/u", "Invalid regular expression: /(?<=a){1}/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })

test("/(?<!a)?/", {}, { ecmaVersion: 2018 })
testFail("/(?<!a)?/u", "Invalid regular expression: /(?<!a)?/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })
test("/(?<!a)+/", {}, { ecmaVersion: 2018 })
testFail("/(?<!a)+/u", "Invalid regular expression: /(?<!a)+/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })
test("/(?<!a)*/", {}, { ecmaVersion: 2018 })
testFail("/(?<!a)*/u", "Invalid regular expression: /(?<!a)*/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })
test("/(?<!a){1}/", {}, { ecmaVersion: 2018 })
testFail("/(?<!a){1}/u", "Invalid regular expression: /(?<!a){1}/: Invalid quantifier (1:1)", { ecmaVersion: 2018 })

test("/(?<=(?<a>\\w){3})f/u", {}, { ecmaVersion: 2018 })
test("/((?<=\\w{3}))f/u", {}, { ecmaVersion: 2018 })
test("/(?<a>(?<=\\w{3}))f/u", {}, { ecmaVersion: 2018 })
test("/(?<!(?<a>\\d){3})f/u", {}, { ecmaVersion: 2018 })
test("/(?<!(?<a>\\D){3})f|f/u", {}, { ecmaVersion: 2018 })
test("/(?<a>(?<!\\D{3}))f|f/u", {}, { ecmaVersion: 2018 })
test("/(?<=(?<a>\\w){3})f/", {}, { ecmaVersion: 2018 })
test("/((?<=\\w{3}))f/", {}, { ecmaVersion: 2018 })
test("/(?<a>(?<=\\w{3}))f/", {}, { ecmaVersion: 2018 })
test("/(?<!(?<a>\\d){3})f/", {}, { ecmaVersion: 2018 })
test("/(?<a>(?<!\\D{3}))f|f/", {}, { ecmaVersion: 2018 })
test("/(?<=(?<fst>.)|(?<snd>.))/u", {}, { ecmaVersion: 2018 })

0 comments on commit eb75208

Please sign in to comment.