Skip to content
Permalink
Browse files
Add support for RegExp lookbehind assertions
https://bugs.webkit.org/show_bug.cgi?id=174931
rdar://33183185

This change implements RegExp lookbehind in the Yarr interpreter.

This change introduces the notion of match direction, either forward or backward.
The forward match direction is the way the current code works, matching disjunciton terms and the subject
string in a right to left manner.  Lookbehind assertions, as defined in the EcmaScript spec, process disjunctions
terms right to left matching the correspondding subject string right to left as well.

Except for the Yarr JIT, almost all of the Yarr code has been touched to account for this backward matching.
An additional Byteterm has been added, HaveCheckedInput, which checks that there is at least as many characters
available in the input stream, but it doesn't move the input stream position.  This is basically a CheckInput,
without moving the input position.  For variable counted terms, we still need to check that we won't try to access
characters beyond the first character of the subject string.  For functions like readSurrogatePairChecked(),
we check for input before calling the funcion.  For new input functions with a try prefix like tryReadBackward,
the function itselfs checks for available input.  After these checks prove that it is safe to access an offset
to the left of the current input position, the actual matching can be performed.

The Yarr parser, parses regular expression in left to right order.  It also computes character offest in forward
order.  When we Byteterm compile, we process backward matching disjunctions right to left.  The parser also has
special handling of forward references within a backward matching parenthetical group.  All such forward references
are saved for that parenthetical group and are processed at the end of the group.  Every one of these forward
reference are check to see if a capture to the right of the forward reference was found, if so the forward
reference is converted to a back reference.

As part of this work, the ByteTerm dumping code was significantly updated to allow for not only dumping of the
ByteCode after it has been generated, but to dump ByteCode while it is being interpreted.  This ByteTerm dumping
while interpreting is enabled with the Interpreter::verbose compile time constant.

Reviewed by Yusuke Suzuki.

* JSTests/stress/regexp-lookbehind.js: New tests.
(arrayToString):
(dumpValue):
(compareArray):
(testRegExp):
* JSTests/test262/config.yaml:
* Source/JavaScriptCore/runtime/RegExp.cpp:
(JSC::RegExp::compile):
(JSC::RegExp::compileMatchOnly):
* Source/JavaScriptCore/yarr/YarrInterpreter.cpp:
(JSC::Yarr::ByteTermDumper::ByteTermDumper):
(JSC::Yarr::ByteTermDumper::unicode):
(JSC::Yarr::Interpreter::InputStream::readForCharacterDump):
(JSC::Yarr::Interpreter::InputStream::tryReadBackward):
(JSC::Yarr::Interpreter::InputStream::tryUncheckInput):
(JSC::Yarr::Interpreter::InputStream::isValidNegativeInputOffset):
(JSC::Yarr::Interpreter::InputStream::dump const):
(JSC::Yarr::Interpreter::checkCharacter):
(JSC::Yarr::Interpreter::checkSurrogatePair):
(JSC::Yarr::Interpreter::checkCasedCharacter):
(JSC::Yarr::Interpreter::checkCharacterClass):
(JSC::Yarr::Interpreter::checkCharacterClassDontAdvanceInputForNonBMP):
(JSC::Yarr::Interpreter::tryConsumeBackReference):
(JSC::Yarr::Interpreter::matchAssertionWordBoundary):
(JSC::Yarr::Interpreter::backtrackPatternCharacter):
(JSC::Yarr::Interpreter::backtrackPatternCasedCharacter):
(JSC::Yarr::Interpreter::matchCharacterClass):
(JSC::Yarr::Interpreter::backtrackCharacterClass):
(JSC::Yarr::Interpreter::matchBackReference):
(JSC::Yarr::Interpreter::backtrackBackReference):
(JSC::Yarr::Interpreter::recordParenthesesMatch):
(JSC::Yarr::Interpreter::matchParenthesesOnceBegin):
(JSC::Yarr::Interpreter::matchParenthesesOnceEnd):
(JSC::Yarr::Interpreter::backtrackParenthesesOnceEnd):
(JSC::Yarr::Interpreter::matchParentheticalAssertionBegin):
(JSC::Yarr::Interpreter::backtrackParentheticalAssertionBegin):
(JSC::Yarr::Interpreter::matchDisjunction):
(JSC::Yarr::ByteCompiler::compile):
(JSC::Yarr::ByteCompiler::haveCheckedInput):
(JSC::Yarr::ByteCompiler::assertionWordBoundary):
(JSC::Yarr::ByteCompiler::atomPatternCharacter):
(JSC::Yarr::ByteCompiler::atomCharacterClass):
(JSC::Yarr::ByteCompiler::atomBackReference):
(JSC::Yarr::ByteCompiler::atomParenthesesOnceBegin):
(JSC::Yarr::ByteCompiler::atomParenthesesTerminalBegin):
(JSC::Yarr::ByteCompiler::atomParenthesesSubpatternBegin):
(JSC::Yarr::ByteCompiler::atomParentheticalAssertionBegin):
(JSC::Yarr::ByteCompiler::atomParentheticalAssertionEnd):
(JSC::Yarr::ByteCompiler::atomParenthesesSubpatternEnd):
(JSC::Yarr::ByteCompiler::atomParenthesesOnceEnd):
(JSC::Yarr::ByteCompiler::atomParenthesesTerminalEnd):
(JSC::Yarr::ByteCompiler::emitDisjunction):
(JSC::Yarr::ByteCompiler::isSafeToRecurse):
(JSC::Yarr::ByteTermDumper::dumpTerm):
(JSC::Yarr::ByteTermDumper::dumpDisjunction):
(JSC::Yarr::Interpreter::InputStream::readPair): Deleted.
(JSC::Yarr::ByteCompiler::dumpDisjunction): Deleted.
* Source/JavaScriptCore/yarr/YarrInterpreter.h:
(JSC::Yarr::ByteTerm::ByteTerm):
(JSC::Yarr::ByteTerm::HaveCheckedInput):
(JSC::Yarr::ByteTerm::WordBoundary):
(JSC::Yarr::ByteTerm::BackReference):
(JSC::Yarr::ByteTerm::isCharacterType):
(JSC::Yarr::ByteTerm::isCasedCharacterType):
(JSC::Yarr::ByteTerm::isCharacterClass):
(JSC::Yarr::ByteTerm::matchDirection):
* Source/JavaScriptCore/yarr/YarrJIT.cpp:
(JSC::Yarr::dumpCompileFailure):
* Source/JavaScriptCore/yarr/YarrJIT.h:
* Source/JavaScriptCore/yarr/YarrParser.h:
(JSC::Yarr::Parser::parseParenthesesBegin):
* Source/JavaScriptCore/yarr/YarrPattern.cpp:
(JSC::Yarr::YarrPatternConstructor::resetForReparsing):
(JSC::Yarr::YarrPatternConstructor::assertionBOL):
(JSC::Yarr::YarrPatternConstructor::atomPatternCharacter):
(JSC::Yarr::YarrPatternConstructor::atomBuiltInCharacterClass):
(JSC::Yarr::YarrPatternConstructor::atomParenthesesSubpatternBegin):
(JSC::Yarr::YarrPatternConstructor::atomParentheticalAssertionBegin):
(JSC::Yarr::YarrPatternConstructor::atomParenthesesEnd):
(JSC::Yarr::YarrPatternConstructor::atomBackReference):
(JSC::Yarr::YarrPatternConstructor::copyDisjunction):
(JSC::Yarr::YarrPatternConstructor::quantifyAtom):
(JSC::Yarr::YarrPatternConstructor::disjunction):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::SavedContext::SavedContext):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::SavedContext::restore):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::ParenthesisContext):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::push):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::pop):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::setInvert):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::invert const):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::setMatchDirection):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::matchDirection const):
(JSC::Yarr::YarrPatternConstructor::ParenthesisContext::reset):
(JSC::Yarr::YarrPatternConstructor::pushParenthesisContext):
(JSC::Yarr::YarrPatternConstructor::popParenthesisContext):
(JSC::Yarr::YarrPatternConstructor::setParenthesisInvert):
(JSC::Yarr::YarrPatternConstructor::parenthesisInvert const):
(JSC::Yarr::YarrPatternConstructor::setParenthesisMatchDirection):
(JSC::Yarr::YarrPatternConstructor::parenthesisMatchDirection const):
(JSC::Yarr::YarrPattern::YarrPattern):
(JSC::Yarr::dumpCharacterClass):
(JSC::Yarr::PatternTerm::dump):
* Source/JavaScriptCore/yarr/YarrPattern.h:
(JSC::Yarr::PatternTerm::PatternTerm):
(JSC::Yarr::PatternTerm::convertToBackreference):
(JSC::Yarr::PatternTerm::setMatchDirection):
(JSC::Yarr::PatternTerm::matchDirection const):
(JSC::Yarr::PatternAlternative::PatternAlternative):
(JSC::Yarr::PatternAlternative::matchDirection const):
(JSC::Yarr::PatternDisjunction::addNewAlternative):
(JSC::Yarr::YarrPattern::resetForReparsing):
* Source/JavaScriptCore/yarr/YarrSyntaxChecker.cpp:
(JSC::Yarr::SyntaxChecker::atomParentheticalAssertionBegin):
* Source/WTF/wtf/PrintStream.cpp:
(WTF::printInternal):
* Source/WTF/wtf/PrintStream.h:
* Source/WebCore/contentextensions/URLFilterParser.cpp:
(WebCore::ContentExtensions::PatternParser::atomParentheticalAssertionBegin):

Canonical link: https://commits.webkit.org/257823@main
  • Loading branch information
msaboff committed Dec 14, 2022
1 parent 12ed214 commit 46e6b3f97425a4a7bb16fc175288903a5f74d5f2
Show file tree
Hide file tree
Showing 14 changed files with 1,564 additions and 529 deletions.
@@ -0,0 +1,203 @@
// With verbose set to false, this test is successful if there is no output. Set verbose to true to see expected matches.
let verbose = false;

function arrayToString(arr)
{
let str = '';
arr.forEach(function(v, index) {
if (typeof v == "string")
str += "\"" + v + "\"";
else
str += v;

if (index != (arr.length - 1)) {
str += ',';
};
});
return "[" + str + "]";
}

function dumpValue(v)
{
if (v === null)
return "<null>";

if (v === undefined)
return "<undefined>";

if (typeof v == "string")
return "\"" + v + "\"";

if (v.length)
return arrayToString(v);

return v;
}

function compareArray(a, b)
{
if (a === null && b === null)
return true;

if (a === null) {
print("### a is null, b is not null");
return false;
}

if (b === null) {
print("### a is not null, b is null");
return false;
}

if (a.length !== b.length) {
print("### a.length: " + a.length + ", b.length: " + b.length);
return false;
}

for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
print("### a[" + i + "]: \"" + a[i] + "\" !== b[" + i + "]: \"" + b[i] + "\"");
return false;
}
}

return true;
}

let testNumber = 0;

function testRegExp(re, str, exp)
{
testNumber++;

let actual = str.match(re);

if (compareArray(exp, actual)) {
if (verbose)
print(dumpValue(str) +".match(" + re.toString() + "), passed ", dumpValue(exp));
} else
print(dumpValue(str) +".match(" + re.toString() + "), FAILED test #" + testNumber + ", Expected ", dumpValue(exp), " got ", dumpValue(actual));
}

// Test 1
testRegExp(/(?<= )Dog/, " Dog", ["Dog"]);
testRegExp(/(?<=A )Dog/, "Walk A Dog", ["Dog"]);
testRegExp(/(?<=A )Dog/, "It's A Dog", ["Dog"]);
testRegExp(/((?<=A ))Dog/, "A Dog", ["Dog", ""]);
testRegExp(/((?<=A ))Dog/, "B Dog", null);

// Test 6
testRegExp(/(?<=(\w*) )Dog/, "Big Dog", ["Dog", "Big"]);
testRegExp(/(?<=(\w*) )Dog/, " B i g Dog", ["Dog", "g"]);
testRegExp(/Dog/, "Brown Dog", ["Dog"]);
testRegExp(/(?<=(\w{3}) )Dog/, "Brown Dog", ["Dog", "own"]);
testRegExp(/(?<=.*)Dog/, "Big Dog", ["Dog"]);

// Test 11
testRegExp(/(?<=(Big) )Dog/, "Big Dog", ["Dog", "Big"]);
testRegExp(/^.(?<=a)/, "a" , ["a"]);
testRegExp(/A (?<=A )Dog/, "A Dog", ["A Dog"]); // FAILS
testRegExp(/(?<=(b*))c/, "abbbbbbc", ["c", "bbbbbb"]);
testRegExp(/(?<=(b+))c/, "abbbbbbc", ["c", "bbbbbb"]);

// Test 16
testRegExp(/(?<=(b\d+))c/, "ab1234c", ["c", "b1234"]);
testRegExp(/(?<=((?:b\d{2})*))c/, "ab12b23b34c", ["c", "b12b23b34"]);
testRegExp(/(?<=((?:b\d{2})+))c/, "ab12b23b34c", ["c", "b12b23b34"]);
testRegExp(/.*(?<=(..|...|....))(.*)/, "xabcd", ["xabcd", "cd", ""]);
testRegExp(/.*(?<=(....|...|..))(.*)/, "xabcd", ["xabcd", "abcd", ""]);

// Test 21
testRegExp(/.*(?<=(xx|...|....))(.*)/, "xabcd", ["xabcd", "bcd", ""]);
testRegExp(/.*(?<=(xx|...))(.*)/, "xxabcd", ["xxabcd", "bcd", ""]);
testRegExp(/(?<=^abc)def/, "abcdef", ["def"]);
testRegExp(/(?<=([abc]+)).\1/, "abcdbc", null);
testRegExp(/(.)(?<=(\1\1))/, "abb", ["b", "b", "bb"]);

// Test 26
testRegExp(/(.)(?<=(\1\1))/i, "abB", ["B", "B", "bB"]);
testRegExp(/(?<=\1([abx]))d/, "abxxd", ["d", "x"]);
testRegExp(/((\w)\w)(?<=\1\2\1)/i, "aabAaBa", ["aB", "aB", "a"]);
testRegExp(/(?<=\1(\w+))c/, "ababc", ["c", "ab"]);
testRegExp(/(?<=\1(\w+))c/, "ababbc", ["c", "b"]);

// Test 31
testRegExp(/(?<=\1(\w+))c/, "ababdc", null);
testRegExp(/(?<=(\w+)\1)c/, "ababc", ["c", "abab"]);
testRegExp(/(?<=(\w){3})def/, "abcdef", ["def", "a"]);
testRegExp(/(?<=\b)[d-f]{3}/,"abc def", ["def"]);
testRegExp(/(?<=\B)\w{3}/, "ab cdef" , ["def"]);

// Test 36
testRegExp(/(?<=\B)(?<=c(?<=\w))\w{3}/, "ab cdef", ["def"]);
testRegExp(/(?<=\b)[d-f]{3}/, "abcdef", null);
testRegExp(/(?<!abc)\w\w\w/, "abcdef", ["abc"]);
testRegExp(/(?<!a.c)\w\w\w/, "abcdef", ["abc"]);
testRegExp(/(?<!a\wc)\w\w\w/, "abcdef", ["abc"]);

// Test 41
testRegExp(/(?<!a[a-z])\w\w\w/, "abcdef", ["abc"]);
testRegExp(/(?<!a[a-z]{2})\w\w\w/, "abcdef", ["abc"]);
testRegExp(/(?<!abc)def/, "abcdef", null);
testRegExp(/(?<!a.c)def/, "abcdef", null);
testRegExp(/(?<=ab\wd)\w\w/, "abcdef", ["ef"]);

// Test 46
testRegExp(/(?<=ab(?=c)\wd)\w\w/, "abcdef", ["ef"]);
testRegExp(/(?<=a\w{3})\w\w/, "abcdef", ["ef"]);
testRegExp(/(?<=a(?=([^a]{2})d)\w{3})\w\w/, "abcdef", ["ef", "bc"]);
testRegExp(/(?<=a(?=([bc]{2}(?<!a{2}))d)\w{3})\w\w/, "abcdef", ["ef", "bc"]);
testRegExp(/^faaao?(?<=^f[oa]+(?=o))/, "faaao", ["faaa"]);

// Test 51
testRegExp(/(?<=a(?=([bc]{2}(?<!a*))d)\w{3})\w\w/, "abcdef", null);
testRegExp(/(?<!abc)\w\w\w/, "abcdef", ["abc"]);
testRegExp(/(?<=^[^a-c]{3})def/, "abcdef", null);
testRegExp(/^(f)oo(?<=^\1o+)$/i, "foo", ["foo", "f"]);
testRegExp(/\w+(?<=$)/gm, "ab\ncd\nefg", ["ab", "cd", "efg"]);

// Test 56
testRegExp(/(?<=(bb*))c/, "abbbbbbc", ["c", "bbbbbb"]);
testRegExp(/(?<=(b*?))c/, "abbbbbbc", ["c", ""]);
testRegExp(/(?<=(b*?))c/, "abbbbbbc", ["c", ""]);
testRegExp(/(?<=(ab*?))c/, "abbbbbbc", ["c", "abbbbbb"]);
testRegExp(/(?<=(bb*?))c/, "abbbbbbc", ["c", "b"]);

// Test 61
testRegExp(/(?<=(b*?))c/i, "abbbbbbc", ["c", ""]);
testRegExp(/(?<=(b*?))c/i, "abbbbbbc", ["c", ""]);
testRegExp(/(?<=(ab*?))c/i, "abbbbbbc", ["c", "abbbbbb"]);
testRegExp(/(?<=(bb*?))c/i, "abbbbbbc", ["c", "b"]);
testRegExp(/(?<=(\u{10000}|\u{10400}|\u{10429}))x/u, "\u{10400}x", ["x", "\u{10400}"]);

// Test 66
testRegExp(/(?<=(\u{10000}|\u{10400}{2}|\u{10429}))x/u, "\u{10400}x", null);
testRegExp(/(?<=(\u{10000}|\u{10400}|\u{10429}))x/ui, "\u{10400}x", ["x", "\u{10400}"]);
testRegExp(/(?<=([^\u{10000}\u{10429}]))x/u, "\u{10400}x", ["x", "\u{10400}"]);
testRegExp(/(?<=([^\u{10000}\u{10429}]))x/ui, "\u{10400}x", ["x", "\u{10400}"]);
testRegExp(/(?<=([^\u{10000}\u{10429}]*))x/u, "\u{10400}\u{10406}x", ["x", "\u{10400}\u{10406}"]);

// Test 71
testRegExp(/(?<=([^\u{10000}\u{10429}]*))x/ui, "\u{10400}\u{10406}x", ["x", "\u{10400}\u{10406}"]);
testRegExp(/(?<=([^\u{10000}\u{10429}]*))x/u, "\u{10401}\u{10400}\u{10406}x", ["x", "\u{10401}\u{10400}\u{10406}"]);
testRegExp(/(?<=([^\u{10000}\u{10429}]*))x/ui, "\u{10401}\u{10400}\u{10406}x", ["x", "\u{10400}\u{10406}"]);
testRegExp(/(?<=([^\u{10000}\u{10429}]*))x/u, "\u{10401}A\u{10400}\u{10406}x", ["x", "\u{10401}A\u{10400}\u{10406}"]);
testRegExp(/(?<=([^\u{10000}\u{10429}]*))x/ui, "\u{10401}A\u{10400}\u{10406}x", ["x", "A\u{10400}\u{10406}"]);

// Test 76
testRegExp(/(?<=(A[^\u{10000}\u{10429}]*))x/u, "A\u{10400}\u{10406}x", ["x", "A\u{10400}\u{10406}"]);
testRegExp(/(?<=(A[^\u{10000}\u{10429}]*))x/ui, "A\u{10400}\u{10406}x", ["x", "A\u{10400}\u{10406}"]);
testRegExp(/(?<=([\u{10401}\u{10402}\u{10403}\u{10404}\u{10405}\u{10406}\u{10407}\u{10408}\u{10409}\u{1040a}]))x/u, "\u{10408}x", ["x", "\u{10408}"]);
testRegExp(/(?<=([\u{10428}\u{1042a}\u{1042c}\u{1042e}]))x/ui, "\u{10404}x", ["x", "\u{10404}"]);
testRegExp(/(?<=(A[\u{10401}\u{10402}\u{10403}\u{10404}\u{10405}\u{10406}\u{10407}\u{10408}\u{10409}\u{1040a}]*))x/u, "A\u{10408}\u{10406}x", ["x", "A\u{10408}\u{10406}"]);

// Test 81
testRegExp(/(?<=(A[\u{10428}\u{1042a}\u{1042c}\u{1042e}]*))x/ui, "A\u{10404}\u{10406}x", ["x", "A\u{10404}\u{10406}"]);
testRegExp(/(?<=(^[^\u{10000}\u{10429}]*))x/u, "\u{10401}A\u{10400}\u{10406}x", ["x", "\u{10401}A\u{10400}\u{10406}"]);
testRegExp(/(?<=((?:A|\u{10400})*))x/u, "\u{10400}A\u{10400}x", ["x", "\u{10400}A\u{10400}"]);
testRegExp(/(?<=((?:A|\u{10400}|\u{10401}|\u{10406})*))x/u, "\u{10401}A\u{10400}\u{10406}x", ["x", "\u{10401}A\u{10400}\u{10406}"]);
testRegExp(/(?<=(^(?:A|\u{10400}|\u{10401}|\u{10406})*))x/u, "\u{10401}A\u{10400}\u{10406}x", ["x", "\u{10401}A\u{10400}\u{10406}"]);

// Test 86
testRegExp(/(?<=(\d{2})(\d{2}))X/, "34121234X", ["X", "12", "34"]);
testRegExp(/(?<=\2\1(\d{2})(\d{2}))X/, "34121234X", ["X", "12", "34"]);
@@ -11,8 +11,6 @@ flags:
resizable-arraybuffer: useArrayBufferTransfer
skip:
features:
# https://bugs.webkit.org/show_bug.cgi?id=174931
- regexp-lookbehind
- regexp-v-flag
- callable-boundary-realms
- FinalizationRegistry.prototype.cleanupSome
@@ -249,6 +249,7 @@ void RegExp::compile(VM* vm, Yarr::CharSize charSize)
#if !ENABLE(YARR_JIT_BACKREFERENCES)
&& !pattern.m_containsBackreferences
#endif
&& !pattern.m_containsLookbehinds
) {
auto& jitCode = ensureRegExpJITCode();
Yarr::jitCompile(pattern, m_patternString, charSize, vm, jitCode, Yarr::JITCompileMode::IncludeSubpatterns);
@@ -261,8 +262,7 @@ void RegExp::compile(VM* vm, Yarr::CharSize charSize)
UNUSED_PARAM(charSize);
#endif

if (Options::dumpCompiledRegExpPatterns())
dataLog("Can't JIT this regular expression: \"", m_patternString, "\"\n");
dataLogLnIf(Options::dumpCompiledRegExpPatterns(), "Can't JIT this regular expression: \"/", m_patternString, "/\"");

m_state = ByteCode;
m_regExpBytecode = byteCodeCompilePattern(vm, pattern, m_constructionErrorCode);
@@ -313,6 +313,7 @@ void RegExp::compileMatchOnly(VM* vm, Yarr::CharSize charSize)
#if !ENABLE(YARR_JIT_BACKREFERENCES)
&& !pattern.m_containsBackreferences
#endif
&& !pattern.m_containsLookbehinds
) {
auto& jitCode = ensureRegExpJITCode();
Yarr::jitCompile(pattern, m_patternString, charSize, vm, jitCode, Yarr::JITCompileMode::MatchOnly);
@@ -325,8 +326,7 @@ void RegExp::compileMatchOnly(VM* vm, Yarr::CharSize charSize)
UNUSED_PARAM(charSize);
#endif

if (Options::dumpCompiledRegExpPatterns())
dataLog("Can't JIT this regular expression: \"", m_patternString, "\"\n");
dataLogLnIf(Options::dumpCompiledRegExpPatterns(), "Can't JIT this regular expression: \"/", m_patternString, "/\"");

m_state = ByteCode;
m_regExpBytecode = byteCodeCompilePattern(vm, pattern, m_constructionErrorCode);

0 comments on commit 46e6b3f

Please sign in to comment.