Regex: Rework parsing, treat lookarounds as assertions, and flags separately
This commit is contained in:
parent
b0233262b8
commit
b8495f0953
|
@ -135,6 +135,8 @@ private:
|
|||
|
||||
AstNodePtr term()
|
||||
{
|
||||
while (flag()) // read all flags
|
||||
{}
|
||||
if (auto node = assertion())
|
||||
return node;
|
||||
if (auto node = atom())
|
||||
|
@ -145,6 +147,34 @@ private:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool peek(StringView expected) const
|
||||
{
|
||||
auto it = m_pos;
|
||||
for (Iterator expected_it{expected.begin(), expected}; expected_it != expected.end(); ++expected_it)
|
||||
{
|
||||
if (it == m_regex.end() or *it++ != *expected_it)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flag()
|
||||
{
|
||||
if (peek("(?i)"))
|
||||
{
|
||||
m_ignore_case = true;
|
||||
m_pos += 4;
|
||||
return true;
|
||||
}
|
||||
if (peek("(?I)"))
|
||||
{
|
||||
m_ignore_case = false;
|
||||
m_pos += 4;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
AstNodePtr assertion()
|
||||
{
|
||||
if (at_end())
|
||||
|
@ -166,6 +196,34 @@ private:
|
|||
case 'K': m_pos += 2; return new_node(ParsedRegex::ResetStart);
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
{
|
||||
Optional<ParsedRegex::Op> lookaround_op;
|
||||
constexpr struct { StringView prefix; ParsedRegex::Op op; } lookarounds[] = {
|
||||
{ "(?=", ParsedRegex::LookAhead },
|
||||
{ "(?!", ParsedRegex::NegativeLookAhead },
|
||||
{ "(?<=", ParsedRegex::LookBehind },
|
||||
{ "(?<!", ParsedRegex::NegativeLookBehind }
|
||||
};
|
||||
for (auto& lookaround : lookarounds)
|
||||
{
|
||||
if (peek(lookaround.prefix))
|
||||
{
|
||||
lookaround_op = lookaround.op;
|
||||
m_pos += (int)lookaround.prefix.char_length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (not lookaround_op)
|
||||
return nullptr;
|
||||
|
||||
AstNodePtr lookaround = alternative(*lookaround_op);
|
||||
if (at_end() or *m_pos++ != ')')
|
||||
parse_error("unclosed parenthesis");
|
||||
|
||||
validate_lookaround(lookaround);
|
||||
return lookaround;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -180,60 +238,18 @@ private:
|
|||
{
|
||||
case '.': ++m_pos; return new_node(ParsedRegex::AnyChar);
|
||||
case '(':
|
||||
{
|
||||
auto advance = [&]() {
|
||||
if (++m_pos == m_regex.end())
|
||||
parse_error("unclosed parenthesis");
|
||||
return *m_pos;
|
||||
};
|
||||
|
||||
AstNodePtr content;
|
||||
if (advance() == '?')
|
||||
{
|
||||
auto c = advance();
|
||||
if (c == ':')
|
||||
{
|
||||
++m_pos;
|
||||
content = disjunction(-1);
|
||||
}
|
||||
else if (contains("=!<", c))
|
||||
bool capture = true;
|
||||
if (peek("?:"))
|
||||
{
|
||||
bool behind = false;
|
||||
if (c == '<')
|
||||
{
|
||||
advance();
|
||||
behind = true;
|
||||
capture = false;
|
||||
m_pos += 2;
|
||||
}
|
||||
|
||||
auto type = *m_pos++;
|
||||
if (type == '=')
|
||||
content = alternative(behind ? ParsedRegex::LookBehind
|
||||
: ParsedRegex::LookAhead);
|
||||
else if (type == '!')
|
||||
content = alternative(behind ? ParsedRegex::NegativeLookBehind
|
||||
: ParsedRegex::NegativeLookAhead);
|
||||
else
|
||||
parse_error("invalid disjunction");
|
||||
|
||||
validate_lookaround(content);
|
||||
}
|
||||
else if (c == 'i' or c == 'I')
|
||||
{
|
||||
m_ignore_case = c == 'i';
|
||||
if (advance() != ')')
|
||||
AstNodePtr content = disjunction(capture ? m_parsed_regex.capture_count++ : -1);
|
||||
if (at_end() or *m_pos++ != ')')
|
||||
parse_error("unclosed parenthesis");
|
||||
++m_pos;
|
||||
return atom(); // get next atom
|
||||
}
|
||||
else
|
||||
parse_error("invalid disjunction");
|
||||
}
|
||||
else
|
||||
content = disjunction(m_parsed_regex.capture_count++);
|
||||
|
||||
if (at_end() or *m_pos != ')')
|
||||
parse_error("unclosed parenthesis");
|
||||
++m_pos;
|
||||
return content;
|
||||
}
|
||||
case '\\':
|
||||
|
@ -473,7 +489,7 @@ private:
|
|||
bool at_end() const { return m_pos == m_regex.end(); }
|
||||
|
||||
[[gnu::noreturn]]
|
||||
void parse_error(StringView error)
|
||||
void parse_error(StringView error) const
|
||||
{
|
||||
throw regex_error(format("regex parse error: {} at '{}<<<HERE>>>{}'", error,
|
||||
StringView{m_regex.begin(), m_pos.base()},
|
||||
|
|
Loading…
Reference in New Issue
Block a user