| //===----------------------------------------------------------------------===// |
| // |
| // 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 |
| /// Pool for Regular Expression AST nodes (Implementation). |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "src/__support/regex/regex_expr_pool.h" |
| #include "hdr/regex_macros.h" |
| #include "src/__support/CPP/new.h" |
| #include "src/__support/alloc-checker.h" |
| #include "src/__support/hash.h" |
| #include "src/__support/macros/config.h" |
| #include "src/__support/macros/null_check.h" |
| #include "src/string/memory_utils/inline_memset.h" |
| |
| namespace LIBC_NAMESPACE_DECL { |
| namespace regex { |
| |
| namespace { |
| |
| // Hash an Expr node for hash-consing. |
| uint64_t hash_expr(const Expr &e) { |
| // Initialise HashState with a constant seed. The specific value (0x12345678) |
| // is an arbitrary placeholder; HashState immediately mixes this seed with |
| // high-entropy constants (derived from aHash) to produce a strong hash, while |
| // the constant value guarantees deterministic hashing for hash-consing. |
| internal::HashState hasher(0x12345678); |
| uint64_t kind = static_cast<uint64_t>(e.kind); |
| hasher.update(&kind, sizeof(kind)); |
| switch (e.kind) { |
| case ExprKind::Literal: |
| hasher.update(&e.ch, sizeof(e.ch)); |
| break; |
| case ExprKind::Concat: |
| case ExprKind::Alt: |
| hasher.update(&e.bin.left, sizeof(e.bin.left)); |
| hasher.update(&e.bin.right, sizeof(e.bin.right)); |
| break; |
| default: |
| break; |
| } |
| return hasher.finish(); |
| } |
| |
| } // namespace |
| |
| ExprPool::ExprPool() { |
| AllocChecker ac; |
| hashtable = new (ac) Expr *[HASH_TABLE_SIZE]; |
| if (ac) { |
| inline_memset(hashtable, 0, HASH_TABLE_SIZE * sizeof(Expr *)); |
| } |
| } |
| |
| ExprPool::~ExprPool() { |
| if (hashtable) |
| delete[] hashtable; |
| Block *b = head; |
| while (b) { |
| Block *next_b = b->next; |
| delete b; |
| b = next_b; |
| } |
| } |
| |
| cpp::expected<Expr *, int> ExprPool::intern(const Expr &e) { |
| if (!hashtable) |
| return cpp::unexpected(REG_ESPACE); |
| |
| // 1. Calculate the initial bucket for the given structural definition. |
| uint64_t h = hash_expr(e); |
| size_t idx = h % HASH_TABLE_SIZE; |
| |
| // 2. Linear Probing: Search for an existing node with identical content. |
| // Because pointers are unique, O(1) comparison is guaranteed if |
| // sub-expressions are already interned. |
| size_t start_idx = idx; |
| while (hashtable[idx]) { |
| if (*hashtable[idx] == e) |
| return hashtable[idx]; |
| idx = (idx + 1) % HASH_TABLE_SIZE; |
| if (idx == start_idx) { |
| // Table full (invariant check: HASH_TABLE_SIZE >> MAX_NODE_LIMIT) |
| return cpp::unexpected(REG_ESPACE); |
| } |
| } |
| |
| // 3. Admission Control: Check the hard limit on AST nodes. |
| if (node_count >= MAX_NODE_LIMIT) |
| return cpp::unexpected(REG_ESPACE); |
| |
| // 4. Arena Allocation: If no matching node found, allocate a stable slot. |
| if (!current || current->used == Block::BLOCK_SIZE) { |
| // New blocks are allocated on demand using AllocChecker. |
| AllocChecker ac; |
| Block *new_block = new (ac) Block(); |
| if (!ac) |
| return cpp::unexpected(REG_ESPACE); |
| if (!head) |
| head = new_block; |
| if (current) |
| current->next = new_block; |
| current = new_block; |
| } |
| |
| // 5. Node Initialisation: Copy the structural definition into the arena. |
| Expr *new_node = ¤t->nodes[current->used]; |
| ++current->used; |
| *new_node = e; |
| hashtable[idx] = new_node; |
| node_count++; |
| return new_node; |
| } |
| |
| cpp::expected<Expr *, int> ExprPool::empty_set() { |
| return intern(Expr::make_empty_set()); |
| } |
| cpp::expected<Expr *, int> ExprPool::empty_str() { |
| return intern(Expr::make_empty_str()); |
| } |
| cpp::expected<Expr *, int> ExprPool::make_lit(char c) { |
| return intern(Expr::make_literal(c)); |
| } |
| |
| cpp::expected<Expr *, int> ExprPool::make_concat(Expr *l, Expr *r) { |
| if (!l || !r) |
| return cpp::unexpected(REG_BADPAT); |
| // Apply basic algebraic identities for concatenation: |
| // 1. Ø · R = R · Ø = Ø (Identity: null set) |
| if (l->kind == ExprKind::EmptySet || r->kind == ExprKind::EmptySet) |
| return empty_set(); |
| // 2. ε · R = R · ε = R (Identity: empty string) |
| if (l->kind == ExprKind::EmptyStr) |
| return r; |
| if (r->kind == ExprKind::EmptyStr) |
| return l; |
| return intern(Expr::make_concat(l, r)); |
| } |
| cpp::expected<Expr *, int> ExprPool::make_alt(Expr *l, Expr *r) { |
| if (!l || !r) |
| return cpp::unexpected(REG_BADPAT); |
| // Apply basic algebraic identities for alternation: |
| // 1. Ø | R = R | Ø = R (Identity: null set) |
| if (l->kind == ExprKind::EmptySet) |
| return r; |
| if (r->kind == ExprKind::EmptySet) |
| return l; |
| // 2. R | R = R (Idempotency) |
| if (l == r) |
| return l; |
| return intern(Expr::make_alt(l, r)); |
| } |
| |
| } // namespace regex |
| } // namespace LIBC_NAMESPACE_DECL |