blob: f1864afd41171afee9bdb948469940148c9f9546 [file] [edit]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Regular expression parser implementation.
///
//===----------------------------------------------------------------------===//
#include "src/__support/regex/regex_parser.h"
#include "hdr/regex_macros.h"
#include "src/__support/CPP/array.h"
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/scope.h"
#include "src/__support/CPP/span.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/alloc-checker.h"
#include "src/__support/error_or.h"
#include "src/__support/macros/config.h"
#include "src/__support/regex/regex_ast.h"
#include "src/__support/regex/regex_charclass.h"
#include "src/__support/regex/regex_expr_pool.h"
namespace LIBC_NAMESPACE_DECL {
namespace regex {
namespace {
// Returns true for backslash escape sequences that represent backreferences,
// boundary assertions, or character-class shorthands rather than escaped
// literal characters.
constexpr bool is_unimplemented_ere_escape(char c) {
switch (c) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'b':
case 'B':
case '<':
case '>':
case 'A':
case 'Z':
case '`':
case '\'':
return true;
default:
return false;
}
}
// Parses a single collating element [[.c.]] or equivalence class [[=c=]].
ErrorOr<uint8_t> parse_coll_elem(cpp::string_view &rem) {
cpp::string_view close_delim = rem.starts_with("[.") ? ".]" : "=]";
rem.remove_prefix(2);
// TODO: Adopt cpp::string_view::find once implemented in
// src/__support/CPP/string_view.h.
size_t delim_pos = 0;
while (delim_pos + 2 <= rem.size() &&
!rem.substr(delim_pos).starts_with(close_delim))
++delim_pos;
if (delim_pos + 2 > rem.size())
return Error(REG_EBRACK);
cpp::string_view elem = rem.substr(0, delim_pos);
rem.remove_prefix(delim_pos + 2);
if (elem.size() == 1)
return static_cast<uint8_t>(elem.front());
if (elem == "one")
return static_cast<uint8_t>('1');
return Error(REG_ECOLLATE);
}
// Parses a single bracket expression item (collating element or single byte).
ErrorOr<uint8_t> parse_bracket_elem(cpp::string_view &rem) {
if (rem.starts_with("[.") || rem.starts_with("[="))
return parse_coll_elem(rem);
uint8_t c = static_cast<uint8_t>(rem.front());
rem.remove_prefix(1);
return c;
}
// Parses a POSIX bracket expression [...], returning a ByteSet node.
ErrorOr<ExprId> parse_bracket_expr(cpp::string_view &rem, int cflags,
ExprPool &pool) {
bool negate = false;
if (rem.starts_with('^')) {
negate = true;
rem.remove_prefix(1);
}
ByteSet bs;
bool first = true;
while (!rem.empty()) {
if (rem.starts_with(']') && !first) {
rem.remove_prefix(1);
if (negate) {
bs.flip();
bs.reset(0);
if (cflags & REG_NEWLINE)
bs.reset('\n');
}
return pool.make_byte_set(bs);
}
first = false;
if (rem.starts_with("[:")) {
rem.remove_prefix(2);
// TODO: Adopt cpp::string_view::find once implemented in
// src/__support/CPP/string_view.h.
size_t delim_pos = 0;
while (delim_pos + 2 <= rem.size() &&
!rem.substr(delim_pos).starts_with(":]"))
++delim_pos;
if (delim_pos + 2 > rem.size())
return Error(REG_EBRACK);
cpp::string_view name = rem.substr(0, delim_pos);
rem.remove_prefix(delim_pos + 2);
if (name == "<" || name == ">")
return Error(REG_BADPAT);
cpp::optional<ByteSet> opt = lookup_posix_class(name);
if (!opt.has_value())
return Error(REG_ECTYPE);
bs |= *opt;
if (rem.starts_with('-') && !rem.starts_with("-]"))
return Error(REG_ERANGE);
continue;
}
ErrorOr<uint8_t> c1 = parse_bracket_elem(rem);
if (!c1.has_value())
return Error(c1.error());
if (rem.starts_with('-') && !rem.starts_with("-]")) {
rem.remove_prefix(1);
if (rem.empty())
return Error(REG_ERANGE);
if (rem.starts_with("[:"))
return Error(REG_ERANGE);
ErrorOr<uint8_t> c2 = parse_bracket_elem(rem);
if (!c2.has_value())
return Error(c2.error());
if (*c1 > *c2)
return Error(REG_ERANGE);
bs.set_range(*c1, *c2);
if (rem.starts_with('-') && !rem.starts_with("-]"))
return Error(REG_ERANGE);
} else {
bs.set(*c1);
}
}
return Error(REG_EBRACK);
}
// Maximum parenthesized group nesting depth to prevent parser stack overflow.
constexpr size_t MAX_GROUP_DEPTH = 128;
struct ParsedAtomStack;
ErrorOr<ExprId> parse_alt(cpp::string_view &rem, int cflags, ExprPool &pool,
size_t depth, size_t &re_nsub,
ParsedAtomStack &stack);
// Parses a single ERE atom and an optional trailing '*', '+', or '?'
// quantifier.
ErrorOr<ExprId> parse_quantified_atom(cpp::string_view &rem, int cflags,
ExprPool &pool, size_t depth,
size_t &re_nsub, ParsedAtomStack &stack) {
char c = rem.front();
rem.remove_prefix(1);
ErrorOr<ExprId> atom = INVALID_EXPR_ID;
bool can_repeat = true;
switch (c) {
case '\\': {
if (rem.empty())
return Error(REG_EESCAPE);
c = rem.front();
rem.remove_prefix(1);
switch (c) {
case 'd':
case 's':
case 'w': {
const ByteSet bs = (c == 'd') ? digit_class_set()
: (c == 's') ? space_class_set()
: word_class_set();
atom = pool.make_byte_set(bs);
break;
}
case 'D':
case 'S':
case 'W': {
ByteSet bs = (c == 'D') ? digit_class_set()
: (c == 'S') ? space_class_set()
: word_class_set();
bs.flip();
bs.reset(0);
if (cflags & REG_NEWLINE)
bs.reset('\n');
atom = pool.make_byte_set(bs);
break;
}
default:
if (is_unimplemented_ere_escape(c))
return Error(REG_BADPAT);
atom = pool.make_lit(c);
break;
}
break;
}
case '.': {
ByteSet bs;
if (cflags & REG_NEWLINE) {
bs.set_range(0, '\n' - 1);
bs.set_range('\n' + 1, BYTE_SET_BITS - 1);
} else {
bs.flip();
}
atom = pool.make_byte_set(bs);
break;
}
case '^':
atom = pool.make_anchor(ANCHOR_BOL);
can_repeat = false;
break;
case '$':
atom = pool.make_anchor(ANCHOR_EOL);
can_repeat = false;
break;
// Valid postfix '*', '+', or '?' is consumed after a repeatable atom below;
// reaching a quantifier at the start of an atom means it has no preceding
// repeatable atom (e.g. "*a", "^*", or doubled "a**").
case '*':
case '+':
case '?':
return Error(REG_BADRPT);
case '{':
if (!rem.empty() && rem.front() >= '0' && rem.front() <= '9')
return Error(REG_BADRPT);
return Error(REG_BADPAT);
case '[':
atom = parse_bracket_expr(rem, cflags, pool);
break;
case '(': {
if (depth >= MAX_GROUP_DEPTH)
return Error(REG_ESPACE);
++re_nsub;
ErrorOr<ExprId> inner =
parse_alt(rem, cflags, pool, depth + 1, re_nsub, stack);
if (!inner.has_value())
return inner;
if (!rem.starts_with(')'))
return Error(REG_EPAREN);
rem.remove_prefix(1);
atom = inner;
break;
}
case ')':
// At depth 0, unmatched ')' is treated as a literal character per
// POSIX.1-2024 9.4.3. At depth > 0, parse_concat stops before ')'.
atom = pool.make_lit(')');
break;
default:
atom = pool.make_lit(c);
break;
}
if (!atom.has_value() || !can_repeat)
return atom;
if (rem.starts_with('*')) {
rem.remove_prefix(1);
return pool.make_star(*atom);
}
if (rem.starts_with('+')) {
rem.remove_prefix(1);
ErrorOr<ExprId> star = pool.make_star(*atom);
if (!star.has_value())
return star;
return pool.make_concat(*atom, *star);
}
if (rem.starts_with('?')) {
rem.remove_prefix(1);
return pool.make_alt(EMPTY_STR_ID, *atom);
}
if (rem.starts_with('{'))
return Error(REG_BADPAT);
return atom;
}
// Shared stack of parsed ExprId nodes across recursive parse_alt and
// parse_concat frames. Keeps a 64-element (256-byte) inline buffer on the
// top-level parse_ere frame, spilling to the heap only for larger patterns.
// TODO: Replace ParsedAtomStack with an AllocChecker-backed SmallVector /
// unique_ptr once available in src/__support/CPP/.
struct ParsedAtomStack {
static constexpr size_t INLINE_ATOM_COUNT = 64;
cpp::array<ExprId, INLINE_ATOM_COUNT> inline_atoms{};
ExprId *heap_atoms = nullptr;
cpp::span<ExprId> atoms{inline_atoms};
size_t count = 0;
bool push(ExprId id) {
if (count >= ExprPool::MAX_NODE_LIMIT)
return false;
if (count == atoms.size()) {
size_t new_cap = atoms.size() * 2;
AllocChecker ac;
ExprId *new_atoms = new (ac) ExprId[new_cap];
if (!ac)
return false;
ExprId *dst = new_atoms;
for (ExprId existing : atoms) {
*dst = existing;
++dst;
}
delete[] heap_atoms;
heap_atoms = new_atoms;
atoms = cpp::span<ExprId>(heap_atoms, new_cap);
}
atoms[count] = id;
++count;
return true;
}
ExprId pop() {
--count;
return atoms[count];
}
};
// Parses a single concatenation branch (sequence of quantified atoms).
ErrorOr<ExprId> parse_concat(cpp::string_view &rem, int cflags, ExprPool &pool,
size_t depth, size_t &re_nsub,
ParsedAtomStack &stack) {
// Collect quantified atoms for this branch on the shared stack, then fold
// right-to-left so right-associative make_concat stays O(1) without orphaned
// prefixes.
size_t atom_count = 0;
while (!rem.empty() && !rem.starts_with('|') &&
!(depth > 0 && rem.starts_with(')'))) {
ErrorOr<ExprId> atom =
parse_quantified_atom(rem, cflags, pool, depth, re_nsub, stack);
if (!atom.has_value())
return atom;
if (!stack.push(*atom))
return Error(REG_ESPACE);
++atom_count;
}
ErrorOr<ExprId> acc = pool.empty_str();
if (!acc.has_value())
return acc;
for (; atom_count > 0; --atom_count) {
acc = pool.make_concat(stack.pop(), *acc);
if (!acc.has_value())
return acc;
}
return acc;
}
// Parses an alternation expression (branches separated by '|').
ErrorOr<ExprId> parse_alt(cpp::string_view &rem, int cflags, ExprPool &pool,
size_t depth, size_t &re_nsub,
ParsedAtomStack &stack) {
if (rem.starts_with('|'))
return Error(REG_BADPAT);
ErrorOr<ExprId> first =
parse_concat(rem, cflags, pool, depth, re_nsub, stack);
if (!first.has_value() || !rem.starts_with('|'))
return first;
if (!stack.push(*first))
return Error(REG_ESPACE);
size_t branch_count = 1;
while (rem.starts_with('|')) {
rem.remove_prefix(1);
if (rem.empty() || rem.starts_with('|') ||
(depth > 0 && rem.starts_with(')')))
return Error(REG_BADPAT);
ErrorOr<ExprId> branch =
parse_concat(rem, cflags, pool, depth, re_nsub, stack);
if (!branch.has_value())
return branch;
if (!stack.push(*branch))
return Error(REG_ESPACE);
++branch_count;
}
// Fold alternation branches right-to-left so right-associative sorted
// make_alt stays O(1) per branch without orphaned spine nodes.
ErrorOr<ExprId> acc = EMPTY_SET_ID;
for (; branch_count > 0; --branch_count) {
acc = pool.make_alt(stack.pop(), *acc);
if (!acc.has_value())
return acc;
}
return acc;
}
} // namespace
// Top-level entry point: parses an ERE pattern starting from alternation at
// depth 0 and optionally returns the total count of parenthesized
// subexpressions.
ErrorOr<ExprId> parse_ere(cpp::string_view pattern, int cflags, ExprPool &pool,
size_t *out_re_nsub) {
cpp::string_view rem = pattern;
size_t re_nsub = 0;
ParsedAtomStack stack;
cpp::scope_exit cleanup([&stack] { delete[] stack.heap_atoms; });
ErrorOr<ExprId> res =
parse_alt(rem, cflags, pool, /*depth=*/0, re_nsub, stack);
if (!res.has_value())
return res;
if (out_re_nsub != nullptr)
*out_re_nsub = re_nsub;
return res;
}
} // namespace regex
} // namespace LIBC_NAMESPACE_DECL