mirror of
https://github.com/hanzoai/redis.git
synced 2026-10-30 18:25:12 +00:00
# Redis Array For years, Redis has been missing a real indexed data structure for the use cases where the index and the spatial relationship of elements are semantic. Hashes give you random lookups, but you have to store an index as a key, and have no range visibility. Lists give you appending and trimming, but what is in the middle remains hard to access. Streams give you append-only events, which is another (useful, indeed) beast. None of these is what you want when the *position itself* has business meaning — slot 37, step 4, row 18552, day from 2934 to 2949, file line 11, 12, 15 and so forth. And, all those types, for different reasons, are all suboptimal when you want a **ring buffer** able to store the latest N observed samples of something. Up to now, users found ways (they always do \o/) using the fact that the data structures that are obvious in this universe are also extremely powerful, if well implemented. But this forces compromises. Arrays handle these index-first requirements natively, and usually with much better memory and CPU usage than the workarounds. If the use case is the right one, Arrays often provide much better space, time and usability at the same time. ## Internal encoding 1. When dense, an Array is essentially a more fancy C array. You don't pay anything for storing the index. 2. Yet, instead of going really flat, arrays are sliced into 4096-element slices, and each slice, when it contains just a few elements, uses a special sparse encoding. When a slice is empty it's just a `NULL` stored in the directory. 3. Small ints, floats, and short strings are pointer-tagged, so they cost zero additional memory beyond the pointer slot itself. 4. When very sparse, a super-directory of windowed directories is used. This allows the data type to be safe, instead of exhibiting pathological space or time behavior. This representation is only triggered when there are more than 8 million elements or very high indexes set. ## Use cases Arrays are mostly stateless if not for the fact that each array remembers the index of the latest added item, allowing `ARINSERT` and `ARRING` to work properly. Otherwise it is a set/get at this index game, with solid support for both setting / getting ranges, server-side scanning, returning only populated elements in a time which is proportional not to the range size, but to the population size. A few concrete examples, that may work as mental models for the set of problems that are similar to them (from the POV of the data modeling). **Thermometer.** A sensor reporting once per minute, with gaps: ``` ARSET temp:room12:day7 123 22.3 ARGETRANGE temp:room12:day7 600 660 # the 10:00–11:00 window, with NULLs ARSCAN temp:room12:day7 600 660 # only populated elements AROP temp:room12:day7 0 1439 MAX # peak of the day, server-side ``` Missing minutes cost little to nothing. Numeric aggregation runs inside Redis. Telemetry, IoT, meter readings, KPI rollups. **Calendar.** A clinic with 96 fifteen-minute slots per day: ``` ARSET sched:room12:day 32 booking:991 ARSCAN sched:room12:day 0 95 # only occupied slots ARGETRANGE sched:room12:day 48 63 # the afternoon full view to render ``` The slot number is the business key in this case. Room booking, parking spaces, warehouse bins, lockers, ... **Ring buffer.** ARRING replaces the classic LPUSH+LTRIM pattern. Imagine remote `dmesg`. ``` ARRING machine:123 200 "[141087.430123]: arm_cpu_init(): cpu 14 online" # Capped to 200 entries ARLASTITEMS machine:123 50 REV # 50 newest first ``` Faster than LPUSH+LTRIM, keep indexed access to past elements. Last-N alarms, recent fraud scores, access history, remote logs, device events. Ok here the use cases are mainly the ones of the old pattern: it is just a better fit and allows to access random items in the middle, aggregate server-side, and so forth. **Workflow.** Step number is the index, value is the status. Gaps are meaningful: ``` ARSET claim:99172 0 received ARSET claim:99172 3 waiting:reviewer42 ARSET claim:99172 5 approved ARGETRANGE claim:99172 0 5 # full workflow view, with NULLs for missing steps ARSCAN claim:99172 0 5 # only steps that have a state ARCOUNT claim:99172 # number of recorded steps ARLEN claim:99172 # highest reached step + 1 ``` **Skills knowledge base for agents.** Arrays are good at representing / grepping into Markdown files: ``` ARSET skill:metal_gpu 0 "...." ARSET skill:metal_gpu 1 "...." ARSET skill:metal_gpu 2 "...." ARGREP skill:metal_gpu - + RE "M3|M4" WITHVALUES ``` ARGREP has EXACT, MATCH, GLOB, RE, you can have multiple predicates, can select AND or OR behavior. **Bulk import results.** Sparse row annotations over millions of rows / CSV / ...: ``` ARSET import:job551 18552 ERR:bad_email ARSCAN import:job551 0 1000000 # Provides only rows that have something ``` ## TLDR If the position is part of the meaning, use an Array. If you want to aggregate or grep remotely, use an Array. Feedback welcome :) --------- Co-authored-by: debing.sun <[email protected]> Co-authored-by: Shubham S Taple <[email protected]> Co-authored-by: Yuan Wang <[email protected]> Co-authored-by: Marc Gravell <[email protected]>
2674 lines
68 KiB
C
2674 lines
68 KiB
C
/*
|
|
tre-compile.c - TRE regex compiler
|
|
|
|
This software is released under a BSD-style license.
|
|
See the file LICENSE for details and copyright.
|
|
|
|
*/
|
|
|
|
/*
|
|
TODO:
|
|
- Fix tre_ast_to_tnfa() to recurse using a stack instead of recursive
|
|
function calls.
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif /* HAVE_CONFIG_H */
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "tre-internal.h"
|
|
#include "tre-mem.h"
|
|
#include "tre-stack.h"
|
|
#include "tre-ast.h"
|
|
#include "tre-parse.h"
|
|
#include "tre-compile.h"
|
|
#include "xmalloc.h"
|
|
|
|
typedef struct {
|
|
const tre_ast_node_t **nodes;
|
|
size_t len;
|
|
size_t cap;
|
|
} tre_ast_node_vec_t;
|
|
|
|
typedef struct {
|
|
unsigned char *bytes;
|
|
size_t len;
|
|
size_t cap;
|
|
} tre_literal_byte_buf_t;
|
|
|
|
static unsigned char
|
|
tre_litopt_fold_byte(unsigned char c)
|
|
{
|
|
return (unsigned char)tre_tolower((tre_cint_t)c);
|
|
}
|
|
|
|
static void
|
|
tre_litopt_free_literal_list(tre_literal_opt_literal_t *literals, size_t count)
|
|
{
|
|
size_t i;
|
|
|
|
if (literals == NULL)
|
|
return;
|
|
for (i = 0; i < count; i++)
|
|
if (literals[i].data != NULL)
|
|
xfree(literals[i].data);
|
|
xfree(literals);
|
|
}
|
|
|
|
static void
|
|
tre_litopt_reset_byte_buf(tre_literal_byte_buf_t *buf)
|
|
{
|
|
if (buf->bytes != NULL)
|
|
xfree(buf->bytes);
|
|
buf->bytes = NULL;
|
|
buf->len = 0;
|
|
buf->cap = 0;
|
|
}
|
|
|
|
static int
|
|
tre_litopt_append_ast_node(tre_ast_node_vec_t *vec, const tre_ast_node_t *node)
|
|
{
|
|
const tre_ast_node_t **new_nodes;
|
|
size_t new_cap;
|
|
|
|
if (vec->len == vec->cap)
|
|
{
|
|
new_cap = vec->cap ? vec->cap * 2 : 8;
|
|
new_nodes = xrealloc(vec->nodes, sizeof(*new_nodes) * new_cap);
|
|
if (new_nodes == NULL)
|
|
return REG_ESPACE;
|
|
vec->nodes = new_nodes;
|
|
vec->cap = new_cap;
|
|
}
|
|
|
|
vec->nodes[vec->len++] = node;
|
|
return REG_OK;
|
|
}
|
|
|
|
static int
|
|
tre_litopt_append_byte(tre_literal_byte_buf_t *buf, unsigned char byte)
|
|
{
|
|
unsigned char *new_bytes;
|
|
size_t new_cap;
|
|
|
|
if (buf->len == buf->cap)
|
|
{
|
|
new_cap = buf->cap ? buf->cap * 2 : 8;
|
|
new_bytes = xrealloc(buf->bytes, new_cap);
|
|
if (new_bytes == NULL)
|
|
return REG_ESPACE;
|
|
buf->bytes = new_bytes;
|
|
buf->cap = new_cap;
|
|
}
|
|
|
|
buf->bytes[buf->len++] = byte;
|
|
return REG_OK;
|
|
}
|
|
|
|
static int
|
|
tre_litopt_append_literal(tre_literal_opt_t *opt,
|
|
const tre_literal_byte_buf_t *buf)
|
|
{
|
|
tre_literal_opt_literal_t *new_literals;
|
|
unsigned char *copy;
|
|
size_t new_count;
|
|
|
|
new_count = opt->num_literals + 1;
|
|
new_literals = xrealloc(opt->literals, sizeof(*new_literals) * new_count);
|
|
if (new_literals == NULL)
|
|
return REG_ESPACE;
|
|
opt->literals = new_literals;
|
|
|
|
copy = xmalloc(buf->len);
|
|
if (copy == NULL)
|
|
return REG_ESPACE;
|
|
memcpy(copy, buf->bytes, buf->len);
|
|
|
|
opt->literals[opt->num_literals].data = copy;
|
|
opt->literals[opt->num_literals].len = buf->len;
|
|
opt->num_literals = new_count;
|
|
return REG_OK;
|
|
}
|
|
|
|
/* Fill the fold table once and group literals by the first byte so the
|
|
* matcher can jump straight to the small set of candidates that can match
|
|
* at a given position. */
|
|
static reg_errcode_t
|
|
tre_litopt_prepare(tre_literal_opt_t *opt)
|
|
{
|
|
size_t counts[256] = { 0 };
|
|
size_t next[256];
|
|
tre_literal_opt_literal_t *grouped;
|
|
size_t i;
|
|
|
|
for (i = 0; i < 256; i++)
|
|
opt->fold_map[i] = tre_litopt_fold_byte((unsigned char)i);
|
|
|
|
memset(opt->start_offsets, 0, sizeof(opt->start_offsets));
|
|
if (opt->num_literals == 0)
|
|
return REG_OK;
|
|
|
|
for (i = 0; i < opt->num_literals; i++)
|
|
counts[opt->literals[i].data[0]]++;
|
|
|
|
for (i = 0; i < 256; i++)
|
|
opt->start_offsets[i + 1] = opt->start_offsets[i] + counts[i];
|
|
|
|
grouped = xmalloc(sizeof(*grouped) * opt->num_literals);
|
|
if (grouped == NULL)
|
|
return REG_ESPACE;
|
|
|
|
memcpy(next, opt->start_offsets, sizeof(next));
|
|
for (i = 0; i < opt->num_literals; i++)
|
|
{
|
|
unsigned char first = opt->literals[i].data[0];
|
|
grouped[next[first]++] = opt->literals[i];
|
|
}
|
|
|
|
xfree(opt->literals);
|
|
opt->literals = grouped;
|
|
return REG_OK;
|
|
}
|
|
|
|
static int
|
|
tre_litopt_is_simple_literal(const tre_ast_node_t *node, unsigned char *byte)
|
|
{
|
|
tre_literal_t *lit;
|
|
|
|
if (node == NULL || node->type != LITERAL)
|
|
return 0;
|
|
lit = node->obj;
|
|
if (IS_SPECIAL(lit) || lit->code_min != lit->code_max)
|
|
return 0;
|
|
if (lit->code_min < 0 || lit->code_min > UCHAR_MAX)
|
|
return 0;
|
|
*byte = (unsigned char)lit->code_min;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
tre_litopt_is_icase_char_union(const tre_ast_node_t *node, int cflags,
|
|
unsigned char *byte)
|
|
{
|
|
tre_union_t *uni;
|
|
unsigned char left, right;
|
|
|
|
if (!(cflags & REG_ICASE) || node == NULL || node->type != UNION)
|
|
return 0;
|
|
|
|
uni = node->obj;
|
|
if (!tre_litopt_is_simple_literal(uni->left, &left)
|
|
|| !tre_litopt_is_simple_literal(uni->right, &right))
|
|
return 0;
|
|
|
|
if (tre_litopt_fold_byte(left) != tre_litopt_fold_byte(right))
|
|
return 0;
|
|
|
|
*byte = tre_litopt_fold_byte(left);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
tre_litopt_is_assertion(const tre_ast_node_t *node, int assertion)
|
|
{
|
|
tre_literal_t *lit;
|
|
|
|
if (node == NULL || node->type != LITERAL)
|
|
return 0;
|
|
lit = node->obj;
|
|
return IS_ASSERTION(lit) && lit->code_max == assertion;
|
|
}
|
|
|
|
static int
|
|
tre_litopt_collect_cat_nodes(const tre_ast_node_t *node, tre_ast_node_vec_t *vec)
|
|
{
|
|
tre_catenation_t *cat;
|
|
int err;
|
|
|
|
if (node->type != CATENATION)
|
|
return tre_litopt_append_ast_node(vec, node);
|
|
|
|
cat = node->obj;
|
|
err = tre_litopt_collect_cat_nodes(cat->left, vec);
|
|
if (err != REG_OK)
|
|
return err;
|
|
return tre_litopt_collect_cat_nodes(cat->right, vec);
|
|
}
|
|
|
|
static int
|
|
tre_litopt_collect_alt_nodes(const tre_ast_node_t *node, int cflags,
|
|
tre_ast_node_vec_t *vec)
|
|
{
|
|
tre_union_t *uni;
|
|
unsigned char byte;
|
|
int err;
|
|
|
|
if (node->type != UNION || tre_litopt_is_icase_char_union(node, cflags, &byte))
|
|
return tre_litopt_append_ast_node(vec, node);
|
|
|
|
uni = node->obj;
|
|
err = tre_litopt_collect_alt_nodes(uni->left, cflags, vec);
|
|
if (err != REG_OK)
|
|
return err;
|
|
return tre_litopt_collect_alt_nodes(uni->right, cflags, vec);
|
|
}
|
|
|
|
static int
|
|
tre_litopt_collect_literal_string(const tre_ast_node_t *node, int cflags,
|
|
tre_literal_byte_buf_t *buf)
|
|
{
|
|
tre_catenation_t *cat;
|
|
unsigned char byte;
|
|
int err;
|
|
|
|
switch (node->type)
|
|
{
|
|
case CATENATION:
|
|
cat = node->obj;
|
|
err = tre_litopt_collect_literal_string(cat->left, cflags, buf);
|
|
if (err != 1)
|
|
return err;
|
|
return tre_litopt_collect_literal_string(cat->right, cflags, buf);
|
|
|
|
case LITERAL:
|
|
if (!tre_litopt_is_simple_literal(node, &byte))
|
|
return 0;
|
|
if (cflags & REG_ICASE)
|
|
byte = tre_litopt_fold_byte(byte);
|
|
return tre_litopt_append_byte(buf, byte) == REG_OK ? 1 : -1;
|
|
|
|
case UNION:
|
|
if (!tre_litopt_is_icase_char_union(node, cflags, &byte))
|
|
return 0;
|
|
return tre_litopt_append_byte(buf, byte) == REG_OK ? 1 : -1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static reg_errcode_t
|
|
tre_litopt_try_compile(tre_tnfa_t *tnfa, const tre_ast_node_t *tree,
|
|
int cflags, int mb_cur_max)
|
|
{
|
|
tre_ast_node_vec_t pieces = { 0 }, alts = { 0 };
|
|
tre_literal_byte_buf_t buf = { 0 };
|
|
tre_literal_opt_t opt = { 0 };
|
|
size_t first, last, i;
|
|
int err;
|
|
|
|
if (mb_cur_max != 1 || (cflags & REG_NEWLINE))
|
|
return REG_OK;
|
|
|
|
err = tre_litopt_collect_cat_nodes(tree, &pieces);
|
|
if (err != REG_OK)
|
|
goto error;
|
|
|
|
first = 0;
|
|
last = pieces.len;
|
|
|
|
if (first < last && tre_litopt_is_assertion(pieces.nodes[first], ASSERT_AT_BOL))
|
|
first++;
|
|
if (first < last && tre_litopt_is_assertion(pieces.nodes[last - 1], ASSERT_AT_EOL))
|
|
last--;
|
|
|
|
if (first == last)
|
|
goto out;
|
|
|
|
if (last - first == 1)
|
|
{
|
|
err = tre_litopt_collect_alt_nodes(pieces.nodes[first], cflags, &alts);
|
|
if (err != REG_OK)
|
|
goto error;
|
|
|
|
for (i = 0; i < alts.len; i++)
|
|
{
|
|
err = tre_litopt_collect_literal_string(alts.nodes[i], cflags, &buf);
|
|
if (err < 0)
|
|
goto error;
|
|
if (err == 0 || buf.len == 0)
|
|
goto out;
|
|
err = tre_litopt_append_literal(&opt, &buf);
|
|
if (err != REG_OK)
|
|
goto error;
|
|
buf.len = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = first; i < last; i++)
|
|
{
|
|
err = tre_litopt_collect_literal_string(pieces.nodes[i], cflags, &buf);
|
|
if (err < 0)
|
|
goto error;
|
|
if (err == 0)
|
|
goto out;
|
|
}
|
|
if (buf.len == 0)
|
|
goto out;
|
|
err = tre_litopt_append_literal(&opt, &buf);
|
|
if (err != REG_OK)
|
|
goto error;
|
|
buf.len = 0;
|
|
}
|
|
|
|
if (opt.num_literals == 0)
|
|
goto out;
|
|
|
|
if (first > 0 && last < pieces.len)
|
|
opt.mode = TRE_LITERAL_OPT_EXACT;
|
|
else if (first > 0)
|
|
opt.mode = TRE_LITERAL_OPT_PREFIX;
|
|
else if (last < pieces.len)
|
|
opt.mode = TRE_LITERAL_OPT_SUFFIX;
|
|
else
|
|
opt.mode = TRE_LITERAL_OPT_CONTAINS;
|
|
opt.nocase = !!(cflags & REG_ICASE);
|
|
err = tre_litopt_prepare(&opt);
|
|
if (err != REG_OK)
|
|
goto error;
|
|
|
|
tnfa->literal_opt = opt;
|
|
opt.literals = NULL;
|
|
opt.num_literals = 0;
|
|
|
|
out:
|
|
if (pieces.nodes != NULL)
|
|
xfree(pieces.nodes);
|
|
if (alts.nodes != NULL)
|
|
xfree(alts.nodes);
|
|
tre_litopt_reset_byte_buf(&buf);
|
|
tre_litopt_free_literal_list(opt.literals, opt.num_literals);
|
|
return REG_OK;
|
|
|
|
error:
|
|
if (pieces.nodes != NULL)
|
|
xfree(pieces.nodes);
|
|
if (alts.nodes != NULL)
|
|
xfree(alts.nodes);
|
|
tre_litopt_reset_byte_buf(&buf);
|
|
tre_litopt_free_literal_list(opt.literals, opt.num_literals);
|
|
return REG_ESPACE;
|
|
}
|
|
|
|
/*
|
|
Algorithms to setup tags so that submatch addressing can be done.
|
|
*/
|
|
|
|
|
|
/* Inserts a catenation node to the root of the tree given in `node'.
|
|
As the left child a new tag with number `tag_id' to `node' is added,
|
|
and the right child is the old root. */
|
|
static reg_errcode_t
|
|
tre_add_tag_left(tre_mem_t mem, tre_ast_node_t *node, int tag_id)
|
|
{
|
|
tre_catenation_t *c;
|
|
|
|
DPRINT(("add_tag_left: tag %d\n", tag_id));
|
|
|
|
c = tre_mem_alloc(mem, sizeof(*c));
|
|
if (c == NULL)
|
|
return REG_ESPACE;
|
|
c->left = tre_ast_new_literal(mem, TAG, tag_id);
|
|
if (c->left == NULL)
|
|
return REG_ESPACE;
|
|
c->right = tre_mem_alloc(mem, sizeof(tre_ast_node_t));
|
|
if (c->right == NULL)
|
|
return REG_ESPACE;
|
|
|
|
c->right->obj = node->obj;
|
|
c->right->type = node->type;
|
|
c->right->nullable = -1;
|
|
c->right->submatch_id = -1;
|
|
c->right->firstpos = NULL;
|
|
c->right->lastpos = NULL;
|
|
c->right->num_tags = 0;
|
|
node->obj = c;
|
|
node->type = CATENATION;
|
|
return REG_OK;
|
|
}
|
|
|
|
/* Inserts a catenation node to the root of the tree given in `node'.
|
|
As the right child a new tag with number `tag_id' to `node' is added,
|
|
and the left child is the old root. */
|
|
static reg_errcode_t
|
|
tre_add_tag_right(tre_mem_t mem, tre_ast_node_t *node, int tag_id)
|
|
{
|
|
tre_catenation_t *c;
|
|
|
|
DPRINT(("tre_add_tag_right: tag %d\n", tag_id));
|
|
|
|
c = tre_mem_alloc(mem, sizeof(*c));
|
|
if (c == NULL)
|
|
return REG_ESPACE;
|
|
c->right = tre_ast_new_literal(mem, TAG, tag_id);
|
|
if (c->right == NULL)
|
|
return REG_ESPACE;
|
|
c->left = tre_mem_alloc(mem, sizeof(tre_ast_node_t));
|
|
if (c->left == NULL)
|
|
return REG_ESPACE;
|
|
|
|
c->left->obj = node->obj;
|
|
c->left->type = node->type;
|
|
c->left->nullable = -1;
|
|
c->left->submatch_id = -1;
|
|
c->left->firstpos = NULL;
|
|
c->left->lastpos = NULL;
|
|
c->left->num_tags = 0;
|
|
node->obj = c;
|
|
node->type = CATENATION;
|
|
return REG_OK;
|
|
}
|
|
|
|
typedef enum {
|
|
ADDTAGS_RECURSE,
|
|
ADDTAGS_AFTER_ITERATION,
|
|
ADDTAGS_AFTER_UNION_LEFT,
|
|
ADDTAGS_AFTER_UNION_RIGHT,
|
|
ADDTAGS_AFTER_CAT_LEFT,
|
|
ADDTAGS_AFTER_CAT_RIGHT,
|
|
ADDTAGS_SET_SUBMATCH_END
|
|
} tre_addtags_symbol_t;
|
|
|
|
|
|
typedef struct {
|
|
int tag;
|
|
int next_tag;
|
|
} tre_tag_states_t;
|
|
|
|
|
|
/* Go through `regset' and set submatch data for submatches that are
|
|
using this tag. */
|
|
static void
|
|
tre_purge_regset(int *regset, tre_tnfa_t *tnfa, int tag)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; regset[i] >= 0; i++)
|
|
{
|
|
int id = regset[i] / 2;
|
|
int start = !(regset[i] % 2);
|
|
DPRINT((" Using tag %d for %s offset of "
|
|
"submatch %d\n", tag,
|
|
start ? "start" : "end", id));
|
|
if (start)
|
|
tnfa->submatch_data[id].so_tag = tag;
|
|
else
|
|
tnfa->submatch_data[id].eo_tag = tag;
|
|
}
|
|
regset[0] = -1;
|
|
}
|
|
|
|
|
|
/* Adds tags to appropriate locations in the parse tree in `tree', so that
|
|
subexpressions marked for submatch addressing can be traced. */
|
|
static reg_errcode_t
|
|
tre_add_tags(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree,
|
|
tre_tnfa_t *tnfa)
|
|
{
|
|
reg_errcode_t status = REG_OK;
|
|
tre_addtags_symbol_t symbol;
|
|
tre_ast_node_t *node = tree; /* Tree node we are currently looking at. */
|
|
size_t bottom = tre_stack_num_items(stack);
|
|
/* True for first pass (counting number of needed tags) */
|
|
int first_pass = (mem == NULL || tnfa == NULL);
|
|
int *regset, *orig_regset;
|
|
unsigned int num_tags = 0; /* Total number of tags. */
|
|
unsigned int num_minimals = 0; /* Number of special minimal tags. */
|
|
unsigned int tag = 0; /* The tag that is to be added next. */
|
|
unsigned int next_tag = 1; /* Next tag to use after this one. */
|
|
int *parents; /* Stack of submatches the current submatch is
|
|
contained in. */
|
|
int minimal_tag = -1; /* Tag that marks the beginning of a minimal match. */
|
|
tre_tag_states_t *saved_states;
|
|
|
|
tre_tag_direction_t direction = TRE_TAG_MINIMIZE;
|
|
if (!first_pass)
|
|
{
|
|
tnfa->end_tag = 0;
|
|
tnfa->minimal_tags[0] = -1;
|
|
}
|
|
|
|
regset = xmalloc(sizeof(*regset) * ((tnfa->num_submatches + 1) * 2));
|
|
if (regset == NULL)
|
|
return REG_ESPACE;
|
|
regset[0] = -1;
|
|
orig_regset = regset;
|
|
|
|
parents = xmalloc(sizeof(*parents) * (tnfa->num_submatches + 1));
|
|
if (parents == NULL)
|
|
{
|
|
xfree(regset);
|
|
return REG_ESPACE;
|
|
}
|
|
parents[0] = -1;
|
|
|
|
saved_states = xmalloc(sizeof(*saved_states) * (tnfa->num_submatches + 1));
|
|
if (saved_states == NULL)
|
|
{
|
|
xfree(regset);
|
|
xfree(parents);
|
|
return REG_ESPACE;
|
|
}
|
|
else
|
|
{
|
|
unsigned int i;
|
|
for (i = 0; i <= tnfa->num_submatches; i++)
|
|
saved_states[i].tag = -1;
|
|
}
|
|
|
|
STACK_PUSH(stack, voidptr, node);
|
|
STACK_PUSH(stack, int, ADDTAGS_RECURSE);
|
|
|
|
while (status == REG_OK && tre_stack_num_items(stack) > bottom)
|
|
{
|
|
symbol = (tre_addtags_symbol_t)tre_stack_pop_int(stack);
|
|
switch (symbol)
|
|
{
|
|
|
|
case ADDTAGS_SET_SUBMATCH_END:
|
|
{
|
|
int id = tre_stack_pop_int(stack);
|
|
int i;
|
|
|
|
/* Add end of this submatch to regset. */
|
|
for (i = 0; regset[i] >= 0; i++);
|
|
regset[i] = id * 2 + 1;
|
|
regset[i + 1] = -1;
|
|
|
|
/* Pop this submatch from the parents stack. */
|
|
for (i = 0; parents[i] >= 0; i++);
|
|
parents[i - 1] = -1;
|
|
break;
|
|
}
|
|
|
|
case ADDTAGS_RECURSE:
|
|
node = tre_stack_pop_voidptr(stack);
|
|
|
|
if (node->submatch_id >= 0)
|
|
{
|
|
int id = node->submatch_id;
|
|
int i;
|
|
|
|
|
|
/* Add start of this submatch to regset. */
|
|
for (i = 0; regset[i] >= 0; i++);
|
|
regset[i] = id * 2;
|
|
regset[i + 1] = -1;
|
|
|
|
if (!first_pass)
|
|
{
|
|
for (i = 0; parents[i] >= 0; i++);
|
|
tnfa->submatch_data[id].parents = NULL;
|
|
if (i > 0)
|
|
{
|
|
int *p = xmalloc(sizeof(*p) * (i + 1));
|
|
if (p == NULL)
|
|
{
|
|
status = REG_ESPACE;
|
|
break;
|
|
}
|
|
assert(tnfa->submatch_data[id].parents == NULL);
|
|
tnfa->submatch_data[id].parents = p;
|
|
for (i = 0; parents[i] >= 0; i++)
|
|
p[i] = parents[i];
|
|
p[i] = -1;
|
|
}
|
|
}
|
|
|
|
/* Add end of this submatch to regset after processing this
|
|
node. */
|
|
STACK_PUSHX(stack, int, node->submatch_id);
|
|
STACK_PUSHX(stack, int, ADDTAGS_SET_SUBMATCH_END);
|
|
}
|
|
|
|
switch (node->type)
|
|
{
|
|
case LITERAL:
|
|
{
|
|
tre_literal_t *lit = node->obj;
|
|
|
|
if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
|
|
{
|
|
int i;
|
|
DPRINT(("Literal %d-%d\n",
|
|
(int)lit->code_min, (int)lit->code_max));
|
|
if (regset[0] >= 0)
|
|
{
|
|
/* Regset is not empty, so add a tag before the
|
|
literal or backref. */
|
|
if (!first_pass)
|
|
{
|
|
status = tre_add_tag_left(mem, node, tag);
|
|
tnfa->tag_directions[tag] = direction;
|
|
if (minimal_tag >= 0)
|
|
{
|
|
DPRINT(("Minimal %d, %d\n", minimal_tag, tag));
|
|
for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
|
|
tnfa->minimal_tags[i] = tag;
|
|
tnfa->minimal_tags[i + 1] = minimal_tag;
|
|
tnfa->minimal_tags[i + 2] = -1;
|
|
minimal_tag = -1;
|
|
num_minimals++;
|
|
}
|
|
tre_purge_regset(regset, tnfa, tag);
|
|
}
|
|
else
|
|
{
|
|
DPRINT((" num_tags = 1\n"));
|
|
node->num_tags = 1;
|
|
}
|
|
|
|
DPRINT((" num_tags++\n"));
|
|
regset[0] = -1;
|
|
tag = next_tag;
|
|
num_tags++;
|
|
next_tag++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(!IS_TAG(lit));
|
|
}
|
|
break;
|
|
}
|
|
case CATENATION:
|
|
{
|
|
tre_catenation_t *cat = node->obj;
|
|
tre_ast_node_t *left = cat->left;
|
|
tre_ast_node_t *right = cat->right;
|
|
int reserved_tag = -1;
|
|
DPRINT(("Catenation, next_tag = %d\n", next_tag));
|
|
|
|
|
|
/* After processing right child. */
|
|
STACK_PUSHX(stack, voidptr, node);
|
|
STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_RIGHT);
|
|
|
|
/* Process right child. */
|
|
STACK_PUSHX(stack, voidptr, right);
|
|
STACK_PUSHX(stack, int, ADDTAGS_RECURSE);
|
|
|
|
/* After processing left child. */
|
|
STACK_PUSHX(stack, int, next_tag + left->num_tags);
|
|
DPRINT((" Pushing %d for after left\n",
|
|
next_tag + left->num_tags));
|
|
if (left->num_tags > 0 && right->num_tags > 0)
|
|
{
|
|
/* Reserve the next tag to the right child. */
|
|
DPRINT((" Reserving next_tag %d to right child\n",
|
|
next_tag));
|
|
reserved_tag = next_tag;
|
|
next_tag++;
|
|
}
|
|
STACK_PUSHX(stack, int, reserved_tag);
|
|
STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_LEFT);
|
|
|
|
/* Process left child. */
|
|
STACK_PUSHX(stack, voidptr, left);
|
|
STACK_PUSHX(stack, int, ADDTAGS_RECURSE);
|
|
|
|
}
|
|
break;
|
|
case ITERATION:
|
|
{
|
|
tre_iteration_t *iter = node->obj;
|
|
DPRINT(("Iteration\n"));
|
|
|
|
if (first_pass)
|
|
{
|
|
STACK_PUSHX(stack, int, regset[0] >= 0 || iter->minimal);
|
|
}
|
|
else
|
|
{
|
|
STACK_PUSHX(stack, int, tag);
|
|
STACK_PUSHX(stack, int, iter->minimal);
|
|
}
|
|
STACK_PUSHX(stack, voidptr, node);
|
|
STACK_PUSHX(stack, int, ADDTAGS_AFTER_ITERATION);
|
|
|
|
STACK_PUSHX(stack, voidptr, iter->arg);
|
|
STACK_PUSHX(stack, int, ADDTAGS_RECURSE);
|
|
|
|
/* Regset is not empty, so add a tag here. */
|
|
if (regset[0] >= 0 || iter->minimal)
|
|
{
|
|
if (!first_pass)
|
|
{
|
|
int i;
|
|
status = tre_add_tag_left(mem, node, tag);
|
|
if (iter->minimal)
|
|
tnfa->tag_directions[tag] = TRE_TAG_MAXIMIZE;
|
|
else
|
|
tnfa->tag_directions[tag] = direction;
|
|
if (minimal_tag >= 0)
|
|
{
|
|
DPRINT(("Minimal %d, %d\n", minimal_tag, tag));
|
|
for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
|
|
tnfa->minimal_tags[i] = tag;
|
|
tnfa->minimal_tags[i + 1] = minimal_tag;
|
|
tnfa->minimal_tags[i + 2] = -1;
|
|
minimal_tag = -1;
|
|
num_minimals++;
|
|
}
|
|
tre_purge_regset(regset, tnfa, tag);
|
|
}
|
|
|
|
DPRINT((" num_tags++\n"));
|
|
regset[0] = -1;
|
|
tag = next_tag;
|
|
num_tags++;
|
|
next_tag++;
|
|
}
|
|
direction = TRE_TAG_MINIMIZE;
|
|
}
|
|
break;
|
|
case UNION:
|
|
{
|
|
tre_union_t *uni = node->obj;
|
|
tre_ast_node_t *left = uni->left;
|
|
tre_ast_node_t *right = uni->right;
|
|
int left_tag;
|
|
int right_tag;
|
|
|
|
if (regset[0] >= 0)
|
|
{
|
|
left_tag = next_tag;
|
|
right_tag = next_tag + 1;
|
|
}
|
|
else
|
|
{
|
|
left_tag = tag;
|
|
right_tag = next_tag;
|
|
}
|
|
|
|
DPRINT(("Union\n"));
|
|
|
|
/* After processing right child. */
|
|
STACK_PUSHX(stack, int, right_tag);
|
|
STACK_PUSHX(stack, int, left_tag);
|
|
STACK_PUSHX(stack, voidptr, regset);
|
|
STACK_PUSHX(stack, int, regset[0] >= 0);
|
|
STACK_PUSHX(stack, voidptr, node);
|
|
STACK_PUSHX(stack, voidptr, right);
|
|
STACK_PUSHX(stack, voidptr, left);
|
|
STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_RIGHT);
|
|
|
|
/* Process right child. */
|
|
STACK_PUSHX(stack, voidptr, right);
|
|
STACK_PUSHX(stack, int, ADDTAGS_RECURSE);
|
|
|
|
/* After processing left child. */
|
|
STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_LEFT);
|
|
|
|
/* Process left child. */
|
|
STACK_PUSHX(stack, voidptr, left);
|
|
STACK_PUSHX(stack, int, ADDTAGS_RECURSE);
|
|
|
|
/* Regset is not empty, so add a tag here. */
|
|
if (regset[0] >= 0)
|
|
{
|
|
if (!first_pass)
|
|
{
|
|
int i;
|
|
status = tre_add_tag_left(mem, node, tag);
|
|
tnfa->tag_directions[tag] = direction;
|
|
if (minimal_tag >= 0)
|
|
{
|
|
DPRINT(("Minimal %d, %d\n", minimal_tag, tag));
|
|
for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
|
|
tnfa->minimal_tags[i] = tag;
|
|
tnfa->minimal_tags[i + 1] = minimal_tag;
|
|
tnfa->minimal_tags[i + 2] = -1;
|
|
minimal_tag = -1;
|
|
num_minimals++;
|
|
}
|
|
tre_purge_regset(regset, tnfa, tag);
|
|
}
|
|
|
|
DPRINT((" num_tags++\n"));
|
|
regset[0] = -1;
|
|
tag = next_tag;
|
|
num_tags++;
|
|
next_tag++;
|
|
}
|
|
|
|
if (node->num_submatches > 0)
|
|
{
|
|
/* The next two tags are reserved for markers. */
|
|
next_tag++;
|
|
tag = next_tag;
|
|
next_tag++;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (node->submatch_id >= 0)
|
|
{
|
|
int i;
|
|
/* Push this submatch on the parents stack. */
|
|
for (i = 0; parents[i] >= 0; i++);
|
|
parents[i] = node->submatch_id;
|
|
parents[i + 1] = -1;
|
|
}
|
|
|
|
break; /* end case: ADDTAGS_RECURSE */
|
|
|
|
case ADDTAGS_AFTER_ITERATION:
|
|
{
|
|
int minimal = 0;
|
|
int enter_tag;
|
|
node = tre_stack_pop_voidptr(stack);
|
|
if (first_pass)
|
|
{
|
|
node->num_tags = ((tre_iteration_t *)node->obj)->arg->num_tags
|
|
+ tre_stack_pop_int(stack);
|
|
minimal_tag = -1;
|
|
}
|
|
else
|
|
{
|
|
minimal = tre_stack_pop_int(stack);
|
|
enter_tag = tre_stack_pop_int(stack);
|
|
if (minimal)
|
|
minimal_tag = enter_tag;
|
|
}
|
|
|
|
DPRINT(("After iteration\n"));
|
|
if (!first_pass)
|
|
{
|
|
DPRINT((" Setting direction to %s\n",
|
|
minimal ? "minimize" : "maximize"));
|
|
if (minimal)
|
|
direction = TRE_TAG_MINIMIZE;
|
|
else
|
|
direction = TRE_TAG_MAXIMIZE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ADDTAGS_AFTER_CAT_LEFT:
|
|
{
|
|
int new_tag = tre_stack_pop_int(stack);
|
|
next_tag = tre_stack_pop_int(stack);
|
|
DPRINT(("After cat left, tag = %d, next_tag = %d\n",
|
|
tag, next_tag));
|
|
if (new_tag >= 0)
|
|
{
|
|
DPRINT((" Setting tag to %d\n", new_tag));
|
|
tag = new_tag;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ADDTAGS_AFTER_CAT_RIGHT:
|
|
DPRINT(("After cat right\n"));
|
|
node = tre_stack_pop_voidptr(stack);
|
|
if (first_pass)
|
|
node->num_tags = ((tre_catenation_t *)node->obj)->left->num_tags
|
|
+ ((tre_catenation_t *)node->obj)->right->num_tags;
|
|
break;
|
|
|
|
case ADDTAGS_AFTER_UNION_LEFT:
|
|
DPRINT(("After union left\n"));
|
|
/* Lift the bottom of the `regset' array so that when processing
|
|
the right operand the items currently in the array are
|
|
invisible. The original bottom was saved at ADDTAGS_UNION and
|
|
will be restored at ADDTAGS_AFTER_UNION_RIGHT below. */
|
|
while (*regset >= 0)
|
|
regset++;
|
|
break;
|
|
|
|
case ADDTAGS_AFTER_UNION_RIGHT:
|
|
{
|
|
int added_tags, tag_left, tag_right;
|
|
tre_ast_node_t *left = tre_stack_pop_voidptr(stack);
|
|
tre_ast_node_t *right = tre_stack_pop_voidptr(stack);
|
|
DPRINT(("After union right\n"));
|
|
node = tre_stack_pop_voidptr(stack);
|
|
added_tags = tre_stack_pop_int(stack);
|
|
if (first_pass)
|
|
{
|
|
node->num_tags = ((tre_union_t *)node->obj)->left->num_tags
|
|
+ ((tre_union_t *)node->obj)->right->num_tags + added_tags
|
|
+ ((node->num_submatches > 0) ? 2 : 0);
|
|
}
|
|
regset = tre_stack_pop_voidptr(stack);
|
|
tag_left = tre_stack_pop_int(stack);
|
|
tag_right = tre_stack_pop_int(stack);
|
|
|
|
/* Add tags after both children, the left child gets a smaller
|
|
tag than the right child. This guarantees that we prefer
|
|
the left child over the right child. */
|
|
/* XXX - This is not always necessary (if the children have
|
|
tags which must be seen for every match of that child). */
|
|
/* XXX - Check if this is the only place where tre_add_tag_right
|
|
is used. If so, use tre_add_tag_left (putting the tag before
|
|
the child as opposed after the child) and throw away
|
|
tre_add_tag_right. */
|
|
if (node->num_submatches > 0)
|
|
{
|
|
if (!first_pass)
|
|
{
|
|
status = tre_add_tag_right(mem, left, tag_left);
|
|
tnfa->tag_directions[tag_left] = TRE_TAG_MAXIMIZE;
|
|
status = tre_add_tag_right(mem, right, tag_right);
|
|
tnfa->tag_directions[tag_right] = TRE_TAG_MAXIMIZE;
|
|
}
|
|
DPRINT((" num_tags += 2\n"));
|
|
num_tags += 2;
|
|
}
|
|
direction = TRE_TAG_MAXIMIZE;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
|
|
} /* end switch(symbol) */
|
|
} /* end while(tre_stack_num_items(stack) > bottom) */
|
|
|
|
if (!first_pass)
|
|
tre_purge_regset(regset, tnfa, tag);
|
|
|
|
if (!first_pass && minimal_tag >= 0)
|
|
{
|
|
int i;
|
|
DPRINT(("Minimal %d, %d\n", minimal_tag, tag));
|
|
for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
|
|
tnfa->minimal_tags[i] = tag;
|
|
tnfa->minimal_tags[i + 1] = minimal_tag;
|
|
tnfa->minimal_tags[i + 2] = -1;
|
|
minimal_tag = -1;
|
|
num_minimals++;
|
|
}
|
|
|
|
DPRINT(("tre_add_tags: %s complete. Number of tags %d.\n",
|
|
first_pass? "First pass" : "Second pass", num_tags));
|
|
|
|
assert(tree->num_tags == num_tags);
|
|
tnfa->end_tag = num_tags;
|
|
tnfa->num_tags = num_tags;
|
|
tnfa->num_minimals = num_minimals;
|
|
xfree(orig_regset);
|
|
xfree(parents);
|
|
xfree(saved_states);
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
AST to TNFA compilation routines.
|
|
*/
|
|
|
|
typedef enum {
|
|
COPY_RECURSE,
|
|
COPY_SET_RESULT_PTR
|
|
} tre_copyast_symbol_t;
|
|
|
|
/* Flags for tre_copy_ast(). */
|
|
#define COPY_REMOVE_TAGS 1
|
|
#define COPY_MAXIMIZE_FIRST_TAG 2
|
|
|
|
static reg_errcode_t
|
|
tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
|
|
int flags, int *pos_add, tre_tag_direction_t *tag_directions,
|
|
tre_ast_node_t **copy, int *max_pos)
|
|
{
|
|
reg_errcode_t status = REG_OK;
|
|
size_t bottom = tre_stack_num_items(stack);
|
|
int num_copied = 0;
|
|
int first_tag = 1;
|
|
tre_ast_node_t **result = copy;
|
|
tre_copyast_symbol_t symbol;
|
|
|
|
STACK_PUSH(stack, voidptr, ast);
|
|
STACK_PUSH(stack, int, COPY_RECURSE);
|
|
|
|
while (status == REG_OK && tre_stack_num_items(stack) > bottom)
|
|
{
|
|
tre_ast_node_t *node;
|
|
if (status != REG_OK)
|
|
break;
|
|
|
|
symbol = (tre_copyast_symbol_t)tre_stack_pop_int(stack);
|
|
switch (symbol)
|
|
{
|
|
case COPY_SET_RESULT_PTR:
|
|
result = tre_stack_pop_voidptr(stack);
|
|
break;
|
|
case COPY_RECURSE:
|
|
node = tre_stack_pop_voidptr(stack);
|
|
switch (node->type)
|
|
{
|
|
case LITERAL:
|
|
{
|
|
tre_literal_t *lit = node->obj;
|
|
int pos = lit->position;
|
|
long min = lit->code_min;
|
|
long max = lit->code_max;
|
|
if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
|
|
{
|
|
/* XXX - e.g. [ab] has only one position but two
|
|
nodes, so we are creating holes in the state space
|
|
here. Not fatal, just wastes memory. */
|
|
pos += *pos_add;
|
|
num_copied++;
|
|
}
|
|
else if (IS_TAG(lit) && (flags & COPY_REMOVE_TAGS))
|
|
{
|
|
/* Change this tag to empty. */
|
|
min = EMPTY;
|
|
max = pos = -1;
|
|
}
|
|
else if (IS_TAG(lit) && (flags & COPY_MAXIMIZE_FIRST_TAG)
|
|
&& first_tag)
|
|
{
|
|
/* Maximize the first tag. */
|
|
tag_directions[max] = TRE_TAG_MAXIMIZE;
|
|
first_tag = 0;
|
|
}
|
|
*result = tre_ast_new_literal(mem, min, max);
|
|
if (*result == NULL) {
|
|
status = REG_ESPACE;
|
|
break;
|
|
}
|
|
if (!IS_SPECIAL(lit)) {
|
|
((tre_literal_t *)(*result)->obj)->u.class = lit->u.class;
|
|
((tre_literal_t *)(*result)->obj)->neg_classes = lit->neg_classes;
|
|
} else if (IS_PARAMETER(lit)) {
|
|
((tre_literal_t *)(*result)->obj)->u.params = lit->u.params;
|
|
}
|
|
|
|
if (pos > *max_pos)
|
|
*max_pos = pos;
|
|
break;
|
|
}
|
|
case UNION:
|
|
{
|
|
tre_union_t *uni = node->obj;
|
|
tre_union_t *tmp;
|
|
*result = tre_ast_new_union(mem, uni->left, uni->right);
|
|
if (*result == NULL)
|
|
{
|
|
status = REG_ESPACE;
|
|
break;
|
|
}
|
|
tmp = (*result)->obj;
|
|
result = &tmp->left;
|
|
STACK_PUSHX(stack, voidptr, uni->right);
|
|
STACK_PUSHX(stack, int, COPY_RECURSE);
|
|
STACK_PUSHX(stack, voidptr, &tmp->right);
|
|
STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR);
|
|
STACK_PUSHX(stack, voidptr, uni->left);
|
|
STACK_PUSHX(stack, int, COPY_RECURSE);
|
|
break;
|
|
}
|
|
case CATENATION:
|
|
{
|
|
tre_catenation_t *cat = node->obj;
|
|
tre_catenation_t *tmp;
|
|
*result = tre_ast_new_catenation(mem, cat->left, cat->right);
|
|
if (*result == NULL)
|
|
{
|
|
status = REG_ESPACE;
|
|
break;
|
|
}
|
|
tmp = (*result)->obj;
|
|
tmp->left = NULL;
|
|
tmp->right = NULL;
|
|
result = &tmp->left;
|
|
|
|
STACK_PUSHX(stack, voidptr, cat->right);
|
|
STACK_PUSHX(stack, int, COPY_RECURSE);
|
|
STACK_PUSHX(stack, voidptr, &tmp->right);
|
|
STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR);
|
|
STACK_PUSHX(stack, voidptr, cat->left);
|
|
STACK_PUSHX(stack, int, COPY_RECURSE);
|
|
break;
|
|
}
|
|
case ITERATION:
|
|
{
|
|
tre_iteration_t *iter = node->obj;
|
|
STACK_PUSHX(stack, voidptr, iter->arg);
|
|
STACK_PUSHX(stack, int, COPY_RECURSE);
|
|
*result = tre_ast_new_iter(mem, iter->arg, iter->min,
|
|
iter->max, iter->minimal);
|
|
if (*result == NULL)
|
|
{
|
|
status = REG_ESPACE;
|
|
break;
|
|
}
|
|
iter = (*result)->obj;
|
|
result = &iter->arg;
|
|
break;
|
|
}
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
*pos_add += num_copied;
|
|
return status;
|
|
}
|
|
|
|
typedef enum {
|
|
EXPAND_RECURSE,
|
|
EXPAND_AFTER_ITER
|
|
} tre_expand_ast_symbol_t;
|
|
|
|
/* Expands each iteration node that has a finite nonzero minimum or maximum
|
|
iteration count to a catenated sequence of copies of the node. */
|
|
static reg_errcode_t
|
|
tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
|
|
tre_tag_direction_t *tag_directions, int *max_depth)
|
|
{
|
|
reg_errcode_t status = REG_OK;
|
|
size_t bottom = tre_stack_num_items(stack);
|
|
int pos_add = 0;
|
|
int pos_add_total = 0;
|
|
int max_pos = 0;
|
|
/* Current approximate matching parameters. */
|
|
int params[TRE_PARAM_LAST];
|
|
/* Approximate parameter nesting level. */
|
|
int params_depth = 0;
|
|
int iter_depth = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < TRE_PARAM_LAST; i++)
|
|
params[i] = TRE_PARAM_DEFAULT;
|
|
|
|
STACK_PUSHR(stack, voidptr, ast);
|
|
STACK_PUSHR(stack, int, EXPAND_RECURSE);
|
|
while (status == REG_OK && tre_stack_num_items(stack) > bottom)
|
|
{
|
|
tre_ast_node_t *node;
|
|
tre_expand_ast_symbol_t symbol;
|
|
|
|
if (status != REG_OK)
|
|
break;
|
|
|
|
DPRINT(("pos_add %d\n", pos_add));
|
|
|
|
symbol = (tre_expand_ast_symbol_t)tre_stack_pop_int(stack);
|
|
node = tre_stack_pop_voidptr(stack);
|
|
switch (symbol)
|
|
{
|
|
case EXPAND_RECURSE:
|
|
switch (node->type)
|
|
{
|
|
case LITERAL:
|
|
{
|
|
tre_literal_t *lit= node->obj;
|
|
if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
|
|
{
|
|
lit->position += pos_add;
|
|
if (lit->position > max_pos)
|
|
max_pos = lit->position;
|
|
}
|
|
break;
|
|
}
|
|
case UNION:
|
|
{
|
|
tre_union_t *uni = node->obj;
|
|
STACK_PUSHX(stack, voidptr, uni->right);
|
|
STACK_PUSHX(stack, int, EXPAND_RECURSE);
|
|
STACK_PUSHX(stack, voidptr, uni->left);
|
|
STACK_PUSHX(stack, int, EXPAND_RECURSE);
|
|
break;
|
|
}
|
|
case CATENATION:
|
|
{
|
|
tre_catenation_t *cat = node->obj;
|
|
STACK_PUSHX(stack, voidptr, cat->right);
|
|
STACK_PUSHX(stack, int, EXPAND_RECURSE);
|
|
STACK_PUSHX(stack, voidptr, cat->left);
|
|
STACK_PUSHX(stack, int, EXPAND_RECURSE);
|
|
break;
|
|
}
|
|
case ITERATION:
|
|
{
|
|
tre_iteration_t *iter = node->obj;
|
|
STACK_PUSHX(stack, int, pos_add);
|
|
STACK_PUSHX(stack, voidptr, node);
|
|
STACK_PUSHX(stack, int, EXPAND_AFTER_ITER);
|
|
STACK_PUSHX(stack, voidptr, iter->arg);
|
|
STACK_PUSHX(stack, int, EXPAND_RECURSE);
|
|
/* If we are going to expand this node at EXPAND_AFTER_ITER
|
|
then don't increase the `pos' fields of the nodes now, it
|
|
will get done when expanding. */
|
|
if (iter->min > 1 || iter->max > 1)
|
|
pos_add = 0;
|
|
iter_depth++;
|
|
DPRINT(("iter\n"));
|
|
break;
|
|
}
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
break;
|
|
case EXPAND_AFTER_ITER:
|
|
{
|
|
tre_iteration_t *iter = node->obj;
|
|
int pos_add_last;
|
|
pos_add = tre_stack_pop_int(stack);
|
|
pos_add_last = pos_add;
|
|
if (iter->min > 1 || iter->max > 1)
|
|
{
|
|
tre_ast_node_t *seq1 = NULL, *seq2 = NULL;
|
|
int j;
|
|
int pos_add_save = pos_add;
|
|
|
|
/* Create a catenated sequence of copies of the node. */
|
|
for (j = 0; j < iter->min; j++)
|
|
{
|
|
tre_ast_node_t *copy;
|
|
/* Remove tags from all but the last copy. */
|
|
int flags = ((j + 1 < iter->min)
|
|
? COPY_REMOVE_TAGS
|
|
: COPY_MAXIMIZE_FIRST_TAG);
|
|
DPRINT((" pos_add %d\n", pos_add));
|
|
pos_add_save = pos_add;
|
|
status = tre_copy_ast(mem, stack, iter->arg, flags,
|
|
&pos_add, tag_directions, ©,
|
|
&max_pos);
|
|
if (status != REG_OK)
|
|
return status;
|
|
if (seq1 != NULL)
|
|
seq1 = tre_ast_new_catenation(mem, seq1, copy);
|
|
else
|
|
seq1 = copy;
|
|
if (seq1 == NULL)
|
|
return REG_ESPACE;
|
|
}
|
|
|
|
if (iter->max == -1)
|
|
{
|
|
/* No upper limit. */
|
|
pos_add_save = pos_add;
|
|
status = tre_copy_ast(mem, stack, iter->arg, 0,
|
|
&pos_add, NULL, &seq2, &max_pos);
|
|
if (status != REG_OK)
|
|
return status;
|
|
seq2 = tre_ast_new_iter(mem, seq2, 0, -1, 0);
|
|
if (seq2 == NULL)
|
|
return REG_ESPACE;
|
|
}
|
|
else
|
|
{
|
|
for (j = iter->min; j < iter->max; j++)
|
|
{
|
|
tre_ast_node_t *copy;
|
|
pos_add_save = pos_add;
|
|
status = tre_copy_ast(mem, stack, iter->arg, 0,
|
|
&pos_add, NULL, ©, &max_pos);
|
|
if (status != REG_OK)
|
|
return status;
|
|
if (seq2 != NULL)
|
|
seq2 = tre_ast_new_catenation(mem, copy, seq2);
|
|
else
|
|
seq2 = copy;
|
|
if (seq2 == NULL)
|
|
return REG_ESPACE;
|
|
seq2 = tre_ast_new_iter(mem, seq2, 0, 1, 0);
|
|
if (seq2 == NULL)
|
|
return REG_ESPACE;
|
|
}
|
|
}
|
|
|
|
pos_add = pos_add_save;
|
|
if (seq1 == NULL)
|
|
seq1 = seq2;
|
|
else if (seq2 != NULL)
|
|
seq1 = tre_ast_new_catenation(mem, seq1, seq2);
|
|
if (seq1 == NULL)
|
|
return REG_ESPACE;
|
|
node->obj = seq1->obj;
|
|
node->type = seq1->type;
|
|
}
|
|
|
|
iter_depth--;
|
|
pos_add_total += pos_add - pos_add_last;
|
|
if (iter_depth == 0)
|
|
pos_add = pos_add_total;
|
|
|
|
/* If approximate parameters are specified, surround the result
|
|
with two parameter setting nodes. The one on the left sets
|
|
the specified parameters, and the one on the right restores
|
|
the old parameters. */
|
|
if (iter->params)
|
|
{
|
|
tre_ast_node_t *tmp_l, *tmp_r, *tmp_node, *node_copy;
|
|
int *old_params;
|
|
|
|
tmp_l = tre_ast_new_literal(mem, PARAMETER, 0);
|
|
if (!tmp_l)
|
|
return REG_ESPACE;
|
|
((tre_literal_t *)tmp_l->obj)->u.params = iter->params;
|
|
iter->params[TRE_PARAM_DEPTH] = params_depth + 1;
|
|
tmp_r = tre_ast_new_literal(mem, PARAMETER, 0);
|
|
if (!tmp_r)
|
|
return REG_ESPACE;
|
|
old_params = tre_mem_alloc(mem, sizeof(*old_params)
|
|
* TRE_PARAM_LAST);
|
|
if (!old_params)
|
|
return REG_ESPACE;
|
|
for (i = 0; i < TRE_PARAM_LAST; i++)
|
|
old_params[i] = params[i];
|
|
((tre_literal_t *)tmp_r->obj)->u.params = old_params;
|
|
old_params[TRE_PARAM_DEPTH] = params_depth;
|
|
/* XXX - this is the only place where ast_new_node is
|
|
needed -- should be moved inside AST module. */
|
|
node_copy = tre_ast_new_node(mem, ITERATION,
|
|
sizeof(tre_iteration_t));
|
|
if (!node_copy)
|
|
return REG_ESPACE;
|
|
node_copy->obj = node->obj;
|
|
tmp_node = tre_ast_new_catenation(mem, tmp_l, node_copy);
|
|
if (!tmp_node)
|
|
return REG_ESPACE;
|
|
tmp_node = tre_ast_new_catenation(mem, tmp_node, tmp_r);
|
|
if (!tmp_node)
|
|
return REG_ESPACE;
|
|
/* Replace the contents of `node' with `tmp_node'. */
|
|
memcpy(node, tmp_node, sizeof(*node));
|
|
node->obj = tmp_node->obj;
|
|
node->type = tmp_node->type;
|
|
params_depth++;
|
|
if (params_depth > *max_depth)
|
|
*max_depth = params_depth;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef TRE_DEBUG
|
|
DPRINT(("Expanded AST:\n"));
|
|
tre_ast_print(ast);
|
|
#endif
|
|
|
|
return status;
|
|
}
|
|
|
|
static tre_pos_and_tags_t *
|
|
tre_set_empty(tre_mem_t mem)
|
|
{
|
|
tre_pos_and_tags_t *new_set;
|
|
|
|
new_set = tre_mem_calloc(mem, sizeof(*new_set));
|
|
if (new_set == NULL)
|
|
return NULL;
|
|
|
|
new_set[0].position = -1;
|
|
new_set[0].code_min = -1;
|
|
new_set[0].code_max = -1;
|
|
|
|
return new_set;
|
|
}
|
|
|
|
static tre_pos_and_tags_t *
|
|
tre_set_one(tre_mem_t mem, int position, long code_min, long code_max,
|
|
tre_ctype_t class, tre_ctype_t *neg_classes, int backref)
|
|
{
|
|
tre_pos_and_tags_t *new_set;
|
|
|
|
new_set = tre_mem_calloc(mem, sizeof(*new_set) * 2);
|
|
if (new_set == NULL)
|
|
return NULL;
|
|
|
|
new_set[0].position = position;
|
|
new_set[0].code_min = code_min;
|
|
new_set[0].code_max = code_max;
|
|
new_set[0].class = class;
|
|
new_set[0].neg_classes = neg_classes;
|
|
new_set[0].backref = backref;
|
|
new_set[1].position = -1;
|
|
new_set[1].code_min = -1;
|
|
new_set[1].code_max = -1;
|
|
|
|
return new_set;
|
|
}
|
|
|
|
static tre_pos_and_tags_t *
|
|
tre_set_union(tre_mem_t mem, tre_pos_and_tags_t *set1, tre_pos_and_tags_t *set2,
|
|
int *tags, int assertions, int *params)
|
|
{
|
|
int s1, s2, i, j;
|
|
tre_pos_and_tags_t *new_set;
|
|
int *new_tags;
|
|
int num_tags;
|
|
|
|
for (num_tags = 0; tags != NULL && tags[num_tags] >= 0; num_tags++);
|
|
for (s1 = 0; set1[s1].position >= 0; s1++);
|
|
for (s2 = 0; set2[s2].position >= 0; s2++);
|
|
new_set = tre_mem_calloc(mem, sizeof(*new_set) * (s1 + s2 + 1));
|
|
if (!new_set )
|
|
return NULL;
|
|
|
|
for (s1 = 0; set1[s1].position >= 0; s1++)
|
|
{
|
|
new_set[s1].position = set1[s1].position;
|
|
new_set[s1].code_min = set1[s1].code_min;
|
|
new_set[s1].code_max = set1[s1].code_max;
|
|
new_set[s1].assertions = set1[s1].assertions | assertions;
|
|
new_set[s1].class = set1[s1].class;
|
|
new_set[s1].neg_classes = set1[s1].neg_classes;
|
|
new_set[s1].backref = set1[s1].backref;
|
|
if (set1[s1].tags == NULL && tags == NULL)
|
|
new_set[s1].tags = NULL;
|
|
else
|
|
{
|
|
for (i = 0; set1[s1].tags != NULL && set1[s1].tags[i] >= 0; i++);
|
|
new_tags = tre_mem_alloc(mem, (sizeof(*new_tags)
|
|
* (i + num_tags + 1)));
|
|
if (new_tags == NULL)
|
|
return NULL;
|
|
for (j = 0; j < i; j++)
|
|
new_tags[j] = set1[s1].tags[j];
|
|
for (i = 0; i < num_tags; i++)
|
|
new_tags[j + i] = tags[i];
|
|
new_tags[j + i] = -1;
|
|
new_set[s1].tags = new_tags;
|
|
}
|
|
if (set1[s1].params)
|
|
new_set[s1].params = set1[s1].params;
|
|
if (params)
|
|
{
|
|
if (!new_set[s1].params)
|
|
new_set[s1].params = params;
|
|
else
|
|
{
|
|
new_set[s1].params = tre_mem_alloc(mem, sizeof(*params) *
|
|
TRE_PARAM_LAST);
|
|
if (!new_set[s1].params)
|
|
return NULL;
|
|
for (i = 0; i < TRE_PARAM_LAST; i++)
|
|
if (params[i] != TRE_PARAM_UNSET)
|
|
new_set[s1].params[i] = params[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (s2 = 0; set2[s2].position >= 0; s2++)
|
|
{
|
|
new_set[s1 + s2].position = set2[s2].position;
|
|
new_set[s1 + s2].code_min = set2[s2].code_min;
|
|
new_set[s1 + s2].code_max = set2[s2].code_max;
|
|
/* XXX - why not | assertions here as well? */
|
|
new_set[s1 + s2].assertions = set2[s2].assertions;
|
|
new_set[s1 + s2].class = set2[s2].class;
|
|
new_set[s1 + s2].neg_classes = set2[s2].neg_classes;
|
|
new_set[s1 + s2].backref = set2[s2].backref;
|
|
if (set2[s2].tags == NULL)
|
|
new_set[s1 + s2].tags = NULL;
|
|
else
|
|
{
|
|
for (i = 0; set2[s2].tags[i] >= 0; i++);
|
|
new_tags = tre_mem_alloc(mem, sizeof(*new_tags) * (i + 1));
|
|
if (new_tags == NULL)
|
|
return NULL;
|
|
for (j = 0; j < i; j++)
|
|
new_tags[j] = set2[s2].tags[j];
|
|
new_tags[j] = -1;
|
|
new_set[s1 + s2].tags = new_tags;
|
|
}
|
|
if (set2[s2].params)
|
|
new_set[s1 + s2].params = set2[s2].params;
|
|
if (params)
|
|
{
|
|
if (!new_set[s1 + s2].params)
|
|
new_set[s1 + s2].params = params;
|
|
else
|
|
{
|
|
new_set[s1 + s2].params = tre_mem_alloc(mem, sizeof(*params) *
|
|
TRE_PARAM_LAST);
|
|
if (!new_set[s1 + s2].params)
|
|
return NULL;
|
|
for (i = 0; i < TRE_PARAM_LAST; i++)
|
|
if (params[i] != TRE_PARAM_UNSET)
|
|
new_set[s1 + s2].params[i] = params[i];
|
|
}
|
|
}
|
|
}
|
|
new_set[s1 + s2].position = -1;
|
|
return new_set;
|
|
}
|
|
|
|
/* Finds the empty path through `node' which is the one that should be
|
|
taken according to POSIX.2 rules, and adds the tags on that path to
|
|
`tags'. `tags' may be NULL. If `num_tags_seen' is not NULL, it is
|
|
set to the number of tags seen on the path. */
|
|
static reg_errcode_t
|
|
tre_match_empty(tre_stack_t *stack, tre_ast_node_t *node, int *tags,
|
|
int *assertions, int *params, int *num_tags_seen,
|
|
int *params_seen)
|
|
{
|
|
tre_literal_t *lit;
|
|
tre_union_t *uni;
|
|
tre_catenation_t *cat;
|
|
tre_iteration_t *iter;
|
|
int i;
|
|
size_t bottom = tre_stack_num_items(stack);
|
|
reg_errcode_t status = REG_OK;
|
|
if (num_tags_seen)
|
|
*num_tags_seen = 0;
|
|
if (params_seen)
|
|
*params_seen = 0;
|
|
|
|
status = tre_stack_push_voidptr(stack, node);
|
|
|
|
/* Walk through the tree recursively. */
|
|
while (status == REG_OK && tre_stack_num_items(stack) > bottom)
|
|
{
|
|
node = tre_stack_pop_voidptr(stack);
|
|
|
|
switch (node->type)
|
|
{
|
|
case LITERAL:
|
|
lit = (tre_literal_t *)node->obj;
|
|
switch (lit->code_min)
|
|
{
|
|
case TAG:
|
|
if (lit->code_max >= 0)
|
|
{
|
|
if (tags != NULL)
|
|
{
|
|
/* Add the tag to `tags'. */
|
|
for (i = 0; tags[i] >= 0; i++)
|
|
if (tags[i] == lit->code_max)
|
|
break;
|
|
if (tags[i] < 0)
|
|
{
|
|
tags[i] = lit->code_max;
|
|
tags[i + 1] = -1;
|
|
}
|
|
}
|
|
if (num_tags_seen)
|
|
(*num_tags_seen)++;
|
|
}
|
|
break;
|
|
case ASSERTION:
|
|
assert(lit->code_max >= 1 && lit->code_max <= ASSERT_LAST);
|
|
if (assertions != NULL)
|
|
*assertions |= lit->code_max;
|
|
break;
|
|
case PARAMETER:
|
|
if (params != NULL)
|
|
for (i = 0; i < TRE_PARAM_LAST; i++)
|
|
params[i] = lit->u.params[i];
|
|
if (params_seen != NULL)
|
|
*params_seen = 1;
|
|
break;
|
|
case EMPTY:
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case UNION:
|
|
/* Subexpressions starting earlier take priority over ones
|
|
starting later, so we prefer the left subexpression over the
|
|
right subexpression. */
|
|
uni = (tre_union_t *)node->obj;
|
|
if (uni->left->nullable)
|
|
STACK_PUSHX(stack, voidptr, uni->left)
|
|
else if (uni->right->nullable)
|
|
STACK_PUSHX(stack, voidptr, uni->right)
|
|
else
|
|
assert(0);
|
|
break;
|
|
|
|
case CATENATION:
|
|
/* The path must go through both children. */
|
|
cat = (tre_catenation_t *)node->obj;
|
|
assert(cat->left->nullable);
|
|
assert(cat->right->nullable);
|
|
STACK_PUSHX(stack, voidptr, cat->left);
|
|
STACK_PUSHX(stack, voidptr, cat->right);
|
|
break;
|
|
|
|
case ITERATION:
|
|
/* A match with an empty string is preferred over no match at
|
|
all, so we go through the argument if possible. */
|
|
iter = (tre_iteration_t *)node->obj;
|
|
if (iter->arg->nullable)
|
|
STACK_PUSHX(stack, voidptr, iter->arg);
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
typedef enum {
|
|
NPFL_RECURSE,
|
|
NPFL_POST_UNION,
|
|
NPFL_POST_CATENATION,
|
|
NPFL_POST_ITERATION
|
|
} tre_npfl_stack_symbol_t;
|
|
|
|
|
|
/* Computes and fills in the fields `nullable', `position`, `firstpos',
|
|
and `lastpos' for the nodes of the AST `tree'; `nextpos' points to an
|
|
integer indicating the next available position, and will be updated on
|
|
return to reflect the number of additional positions assigned. */
|
|
static reg_errcode_t
|
|
tre_compute_npfl(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree,
|
|
int *nextpos)
|
|
{
|
|
size_t bottom = tre_stack_num_items(stack);
|
|
|
|
STACK_PUSHR(stack, voidptr, tree);
|
|
STACK_PUSHR(stack, int, NPFL_RECURSE);
|
|
|
|
while (tre_stack_num_items(stack) > bottom)
|
|
{
|
|
tre_npfl_stack_symbol_t symbol;
|
|
tre_ast_node_t *node;
|
|
|
|
symbol = (tre_npfl_stack_symbol_t)tre_stack_pop_int(stack);
|
|
node = tre_stack_pop_voidptr(stack);
|
|
switch (symbol)
|
|
{
|
|
case NPFL_RECURSE:
|
|
switch (node->type)
|
|
{
|
|
case LITERAL:
|
|
{
|
|
tre_literal_t *lit = (tre_literal_t *)node->obj;
|
|
if (IS_BACKREF(lit))
|
|
{
|
|
/* Back references: nullable = false, firstpos = {i},
|
|
lastpos = {i}. */
|
|
node->nullable = 0;
|
|
lit->position = (*nextpos)++;
|
|
node->firstpos = tre_set_one(mem, lit->position, 0,
|
|
TRE_CHAR_MAX, 0, NULL, -1);
|
|
if (!node->firstpos)
|
|
return REG_ESPACE;
|
|
node->lastpos = tre_set_one(mem, lit->position, 0,
|
|
TRE_CHAR_MAX, 0, NULL,
|
|
lit->code_max);
|
|
if (!node->lastpos)
|
|
return REG_ESPACE;
|
|
}
|
|
else if (lit->code_min < 0)
|
|
{
|
|
/* Tags, empty strings, params, and zero width assertions:
|
|
nullable = true, firstpos = {}, and lastpos = {}. */
|
|
node->nullable = 1;
|
|
node->firstpos = tre_set_empty(mem);
|
|
if (!node->firstpos)
|
|
return REG_ESPACE;
|
|
node->lastpos = tre_set_empty(mem);
|
|
if (!node->lastpos)
|
|
return REG_ESPACE;
|
|
}
|
|
else
|
|
{
|
|
/* Literal at position i: nullable = false, firstpos = {i},
|
|
lastpos = {i}. */
|
|
node->nullable = 0;
|
|
lit->position = (*nextpos)++;
|
|
node->firstpos =
|
|
tre_set_one(mem, lit->position, lit->code_min,
|
|
lit->code_max, 0, NULL, -1);
|
|
if (!node->firstpos)
|
|
return REG_ESPACE;
|
|
node->lastpos = tre_set_one(mem, lit->position,
|
|
lit->code_min,
|
|
lit->code_max,
|
|
lit->u.class, lit->neg_classes,
|
|
-1);
|
|
if (!node->lastpos)
|
|
return REG_ESPACE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case UNION:
|
|
/* Compute the attributes for the two subtrees, and after that
|
|
for this node. */
|
|
STACK_PUSHR(stack, voidptr, node);
|
|
STACK_PUSHR(stack, int, NPFL_POST_UNION);
|
|
STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->right);
|
|
STACK_PUSHR(stack, int, NPFL_RECURSE);
|
|
STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->left);
|
|
STACK_PUSHR(stack, int, NPFL_RECURSE);
|
|
break;
|
|
|
|
case CATENATION:
|
|
/* Compute the attributes for the two subtrees, and after that
|
|
for this node. */
|
|
STACK_PUSHR(stack, voidptr, node);
|
|
STACK_PUSHR(stack, int, NPFL_POST_CATENATION);
|
|
STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->right);
|
|
STACK_PUSHR(stack, int, NPFL_RECURSE);
|
|
STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->left);
|
|
STACK_PUSHR(stack, int, NPFL_RECURSE);
|
|
break;
|
|
|
|
case ITERATION:
|
|
/* Compute the attributes for the subtree, and after that for
|
|
this node. */
|
|
STACK_PUSHR(stack, voidptr, node);
|
|
STACK_PUSHR(stack, int, NPFL_POST_ITERATION);
|
|
STACK_PUSHR(stack, voidptr, ((tre_iteration_t *)node->obj)->arg);
|
|
STACK_PUSHR(stack, int, NPFL_RECURSE);
|
|
break;
|
|
}
|
|
break; /* end case: NPFL_RECURSE */
|
|
|
|
case NPFL_POST_UNION:
|
|
{
|
|
tre_union_t *uni = (tre_union_t *)node->obj;
|
|
node->nullable = uni->left->nullable || uni->right->nullable;
|
|
node->firstpos = tre_set_union(mem, uni->left->firstpos,
|
|
uni->right->firstpos, NULL, 0, NULL);
|
|
if (!node->firstpos)
|
|
return REG_ESPACE;
|
|
node->lastpos = tre_set_union(mem, uni->left->lastpos,
|
|
uni->right->lastpos, NULL, 0, NULL);
|
|
if (!node->lastpos)
|
|
return REG_ESPACE;
|
|
break;
|
|
}
|
|
|
|
case NPFL_POST_ITERATION:
|
|
{
|
|
tre_iteration_t *iter = (tre_iteration_t *)node->obj;
|
|
|
|
if (iter->min == 0 || iter->arg->nullable)
|
|
node->nullable = 1;
|
|
else
|
|
node->nullable = 0;
|
|
node->firstpos = iter->arg->firstpos;
|
|
node->lastpos = iter->arg->lastpos;
|
|
break;
|
|
}
|
|
|
|
case NPFL_POST_CATENATION:
|
|
{
|
|
int num_tags, *tags, assertions, params_seen;
|
|
int *params;
|
|
reg_errcode_t status;
|
|
tre_catenation_t *cat = node->obj;
|
|
node->nullable = cat->left->nullable && cat->right->nullable;
|
|
|
|
/* Compute firstpos. */
|
|
if (cat->left->nullable)
|
|
{
|
|
/* The left side matches the empty string. Make a first pass
|
|
with tre_match_empty() to get the number of tags and
|
|
parameters. */
|
|
status = tre_match_empty(stack, cat->left,
|
|
NULL, NULL, NULL, &num_tags,
|
|
¶ms_seen);
|
|
if (status != REG_OK)
|
|
return status;
|
|
/* Allocate arrays for the tags and parameters. */
|
|
tags = xmalloc(sizeof(*tags) * (num_tags + 1));
|
|
if (!tags)
|
|
return REG_ESPACE;
|
|
tags[0] = -1;
|
|
assertions = 0;
|
|
params = NULL;
|
|
if (params_seen)
|
|
{
|
|
params = tre_mem_alloc(mem, sizeof(*params)
|
|
* TRE_PARAM_LAST);
|
|
if (!params)
|
|
{
|
|
xfree(tags);
|
|
return REG_ESPACE;
|
|
}
|
|
}
|
|
/* Second pass with tre_mach_empty() to get the list of
|
|
tags and parameters. */
|
|
status = tre_match_empty(stack, cat->left, tags,
|
|
&assertions, params, NULL, NULL);
|
|
if (status != REG_OK)
|
|
{
|
|
xfree(tags);
|
|
return status;
|
|
}
|
|
node->firstpos =
|
|
tre_set_union(mem, cat->right->firstpos, cat->left->firstpos,
|
|
tags, assertions, params);
|
|
xfree(tags);
|
|
if (!node->firstpos)
|
|
return REG_ESPACE;
|
|
}
|
|
else
|
|
{
|
|
node->firstpos = cat->left->firstpos;
|
|
}
|
|
|
|
/* Compute lastpos. */
|
|
if (cat->right->nullable)
|
|
{
|
|
/* The right side matches the empty string. Make a first pass
|
|
with tre_match_empty() to get the number of tags and
|
|
parameters. */
|
|
status = tre_match_empty(stack, cat->right,
|
|
NULL, NULL, NULL, &num_tags,
|
|
¶ms_seen);
|
|
if (status != REG_OK)
|
|
return status;
|
|
/* Allocate arrays for the tags and parameters. */
|
|
tags = xmalloc(sizeof(*tags) * (num_tags + 1));
|
|
if (!tags)
|
|
return REG_ESPACE;
|
|
tags[0] = -1;
|
|
assertions = 0;
|
|
params = NULL;
|
|
if (params_seen)
|
|
{
|
|
params = tre_mem_alloc(mem, sizeof(*params)
|
|
* TRE_PARAM_LAST);
|
|
if (!params)
|
|
{
|
|
xfree(tags);
|
|
return REG_ESPACE;
|
|
}
|
|
}
|
|
/* Second pass with tre_mach_empty() to get the list of
|
|
tags and parameters. */
|
|
status = tre_match_empty(stack, cat->right, tags,
|
|
&assertions, params, NULL, NULL);
|
|
if (status != REG_OK)
|
|
{
|
|
xfree(tags);
|
|
return status;
|
|
}
|
|
node->lastpos =
|
|
tre_set_union(mem, cat->left->lastpos, cat->right->lastpos,
|
|
tags, assertions, params);
|
|
xfree(tags);
|
|
if (!node->lastpos)
|
|
return REG_ESPACE;
|
|
}
|
|
else
|
|
{
|
|
node->lastpos = cat->right->lastpos;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return REG_OK;
|
|
}
|
|
|
|
|
|
/* Adds a transition from each position in `p1' to each position in `p2'. */
|
|
static reg_errcode_t
|
|
tre_make_trans(tre_pos_and_tags_t *p1, tre_pos_and_tags_t *p2,
|
|
tre_tnfa_transition_t *transitions,
|
|
int *counts, int *offs)
|
|
{
|
|
tre_pos_and_tags_t *orig_p2 = p2;
|
|
tre_tnfa_transition_t *trans;
|
|
int i, j, k, l, dup, prev_p2_pos;
|
|
|
|
if (transitions != NULL)
|
|
while (p1->position >= 0)
|
|
{
|
|
p2 = orig_p2;
|
|
prev_p2_pos = -1;
|
|
while (p2->position >= 0)
|
|
{
|
|
/* Optimization: if this position was already handled, skip it. */
|
|
if (p2->position == prev_p2_pos)
|
|
{
|
|
p2++;
|
|
continue;
|
|
}
|
|
prev_p2_pos = p2->position;
|
|
/* Set `trans' to point to the next unused transition from
|
|
position `p1->position'. */
|
|
trans = transitions + offs[p1->position];
|
|
while (trans->state != NULL)
|
|
{
|
|
#if 0
|
|
/* If we find a previous transition from `p1->position' to
|
|
`p2->position', it is overwritten. This can happen only
|
|
if there are nested loops in the regexp, like in "((a)*)*".
|
|
In POSIX.2 repetition using the outer loop is always
|
|
preferred over using the inner loop. Therefore the
|
|
transition for the inner loop is useless and can be thrown
|
|
away. */
|
|
/* XXX - The same position is used for all nodes in a bracket
|
|
expression, so this optimization cannot be used (it will
|
|
break bracket expressions) unless I figure out a way to
|
|
detect it here. */
|
|
if (trans->state_id == p2->position)
|
|
{
|
|
DPRINT(("*"));
|
|
break;
|
|
}
|
|
#endif
|
|
trans++;
|
|
}
|
|
|
|
if (trans->state == NULL)
|
|
(trans + 1)->state = NULL;
|
|
/* Use the character ranges, assertions, etc. from `p1' for
|
|
the transition from `p1' to `p2'. */
|
|
trans->code_min = (tre_cint_t) p1->code_min;
|
|
trans->code_max = (tre_cint_t) p1->code_max;
|
|
trans->state = transitions + offs[p2->position];
|
|
trans->state_id = p2->position;
|
|
trans->assertions = p1->assertions | p2->assertions
|
|
| (p1->class ? ASSERT_CHAR_CLASS : 0)
|
|
| (p1->neg_classes != NULL ? ASSERT_CHAR_CLASS_NEG : 0);
|
|
if (p1->backref >= 0)
|
|
{
|
|
assert((trans->assertions & ASSERT_CHAR_CLASS) == 0);
|
|
assert(p2->backref < 0);
|
|
trans->u.backref = p1->backref;
|
|
trans->assertions |= ASSERT_BACKREF;
|
|
}
|
|
else
|
|
trans->u.class = p1->class;
|
|
if (p1->neg_classes != NULL)
|
|
{
|
|
for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++);
|
|
trans->neg_classes =
|
|
xmalloc(sizeof(*trans->neg_classes) * (i + 1));
|
|
if (trans->neg_classes == NULL)
|
|
return REG_ESPACE;
|
|
for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++)
|
|
trans->neg_classes[i] = p1->neg_classes[i];
|
|
trans->neg_classes[i] = (tre_ctype_t)0;
|
|
}
|
|
else
|
|
trans->neg_classes = NULL;
|
|
|
|
/* Find out how many tags this transition has. */
|
|
i = 0;
|
|
if (p1->tags != NULL)
|
|
while(p1->tags[i] >= 0)
|
|
i++;
|
|
j = 0;
|
|
if (p2->tags != NULL)
|
|
while(p2->tags[j] >= 0)
|
|
j++;
|
|
|
|
/* If we are overwriting a transition, free the old tag array. */
|
|
if (trans->tags != NULL)
|
|
xfree(trans->tags);
|
|
trans->tags = NULL;
|
|
|
|
/* If there were any tags, allocate an array and fill it. */
|
|
if (i + j > 0)
|
|
{
|
|
trans->tags = xmalloc(sizeof(*trans->tags) * (i + j + 1));
|
|
if (!trans->tags)
|
|
return REG_ESPACE;
|
|
i = 0;
|
|
if (p1->tags != NULL)
|
|
while(p1->tags[i] >= 0)
|
|
{
|
|
trans->tags[i] = p1->tags[i];
|
|
i++;
|
|
}
|
|
l = i;
|
|
j = 0;
|
|
if (p2->tags != NULL)
|
|
while (p2->tags[j] >= 0)
|
|
{
|
|
/* Don't add duplicates. */
|
|
dup = 0;
|
|
for (k = 0; k < i; k++)
|
|
if (trans->tags[k] == p2->tags[j])
|
|
{
|
|
dup = 1;
|
|
break;
|
|
}
|
|
if (!dup)
|
|
trans->tags[l++] = p2->tags[j];
|
|
j++;
|
|
}
|
|
trans->tags[l] = -1;
|
|
}
|
|
|
|
/* Set the parameter array. If both `p2' and `p1' have same
|
|
parameters, the values in `p2' override those in `p1'. */
|
|
if (p1->params || p2->params)
|
|
{
|
|
if (!trans->params)
|
|
trans->params = xmalloc(sizeof(*trans->params)
|
|
* TRE_PARAM_LAST);
|
|
if (!trans->params)
|
|
return REG_ESPACE;
|
|
for (i = 0; i < TRE_PARAM_LAST; i++)
|
|
{
|
|
trans->params[i] = TRE_PARAM_UNSET;
|
|
if (p1->params && p1->params[i] != TRE_PARAM_UNSET)
|
|
trans->params[i] = p1->params[i];
|
|
if (p2->params && p2->params[i] != TRE_PARAM_UNSET)
|
|
trans->params[i] = p2->params[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (trans->params)
|
|
xfree(trans->params);
|
|
trans->params = NULL;
|
|
}
|
|
|
|
|
|
#ifdef TRE_DEBUG
|
|
{
|
|
int *tags;
|
|
|
|
DPRINT((" %2d -> %2d on %3d", p1->position, p2->position,
|
|
p1->code_min));
|
|
if (p1->code_max != p1->code_min)
|
|
DPRINT(("-%3d", p1->code_max));
|
|
tags = trans->tags;
|
|
if (tags)
|
|
{
|
|
DPRINT((", tags ["));
|
|
while (*tags >= 0)
|
|
{
|
|
DPRINT(("%d", *tags));
|
|
tags++;
|
|
if (*tags >= 0)
|
|
DPRINT((","));
|
|
}
|
|
DPRINT(("]"));
|
|
}
|
|
if (trans->assertions)
|
|
DPRINT((", assert %d", trans->assertions));
|
|
if (trans->assertions & ASSERT_BACKREF)
|
|
DPRINT((", backref %d", trans->u.backref));
|
|
else if (trans->u.class)
|
|
DPRINT((", class %ld", (long)trans->u.class));
|
|
if (trans->neg_classes)
|
|
DPRINT((", neg_classes %p", trans->neg_classes));
|
|
if (trans->params)
|
|
{
|
|
DPRINT((", "));
|
|
tre_print_params(trans->params);
|
|
}
|
|
DPRINT(("\n"));
|
|
}
|
|
#endif /* TRE_DEBUG */
|
|
p2++;
|
|
}
|
|
p1++;
|
|
}
|
|
else
|
|
/* Compute a maximum limit for the number of transitions leaving
|
|
from each state. */
|
|
while (p1->position >= 0)
|
|
{
|
|
p2 = orig_p2;
|
|
while (p2->position >= 0)
|
|
{
|
|
counts[p1->position]++;
|
|
p2++;
|
|
}
|
|
p1++;
|
|
}
|
|
return REG_OK;
|
|
}
|
|
|
|
/* Converts the syntax tree to a TNFA. All the transitions in the TNFA are
|
|
labelled with one character range (there are no transitions on empty
|
|
strings). The TNFA takes O(n^2) space in the worst case, `n' is size of
|
|
the regexp. */
|
|
static reg_errcode_t
|
|
tre_ast_to_tnfa(tre_ast_node_t *node, tre_tnfa_transition_t *transitions,
|
|
int *counts, int *offs)
|
|
{
|
|
tre_union_t *uni;
|
|
tre_catenation_t *cat;
|
|
tre_iteration_t *iter;
|
|
reg_errcode_t errcode = REG_OK;
|
|
|
|
/* XXX - recurse using a stack!. */
|
|
switch (node->type)
|
|
{
|
|
case LITERAL:
|
|
break;
|
|
case UNION:
|
|
uni = (tre_union_t *)node->obj;
|
|
errcode = tre_ast_to_tnfa(uni->left, transitions, counts, offs);
|
|
if (errcode != REG_OK)
|
|
return errcode;
|
|
errcode = tre_ast_to_tnfa(uni->right, transitions, counts, offs);
|
|
break;
|
|
|
|
case CATENATION:
|
|
cat = (tre_catenation_t *)node->obj;
|
|
/* Add a transition from each position in cat->left->lastpos
|
|
to each position in cat->right->firstpos. */
|
|
errcode = tre_make_trans(cat->left->lastpos, cat->right->firstpos,
|
|
transitions, counts, offs);
|
|
if (errcode != REG_OK)
|
|
return errcode;
|
|
errcode = tre_ast_to_tnfa(cat->left, transitions, counts, offs);
|
|
if (errcode != REG_OK)
|
|
return errcode;
|
|
errcode = tre_ast_to_tnfa(cat->right, transitions, counts, offs);
|
|
break;
|
|
|
|
case ITERATION:
|
|
iter = (tre_iteration_t *)node->obj;
|
|
assert(iter->max == -1 || iter->max == 1);
|
|
|
|
if (iter->max == -1)
|
|
{
|
|
assert(iter->min == 0 || iter->min == 1);
|
|
/* Add a transition from each last position in the iterated
|
|
expression to each first position. */
|
|
errcode = tre_make_trans(iter->arg->lastpos, iter->arg->firstpos,
|
|
transitions, counts, offs);
|
|
if (errcode != REG_OK)
|
|
return errcode;
|
|
}
|
|
errcode = tre_ast_to_tnfa(iter->arg, transitions, counts, offs);
|
|
break;
|
|
}
|
|
return errcode;
|
|
}
|
|
|
|
#define ERROR_EXIT(err) \
|
|
do \
|
|
{ \
|
|
errcode = err; \
|
|
if (/*CONSTCOND*/(void)1,1) \
|
|
goto error_exit; \
|
|
} \
|
|
while (/*CONSTCOND*/(void)0,0)
|
|
|
|
|
|
int
|
|
tre_compile(regex_t *preg, const tre_char_t *regex, size_t n, int cflags)
|
|
{
|
|
tre_stack_t *stack;
|
|
tre_ast_node_t *tree, *tmp_ast_l, *tmp_ast_r;
|
|
tre_pos_and_tags_t *p;
|
|
int *counts = NULL, *offs = NULL;
|
|
int i, add = 0;
|
|
tre_tnfa_transition_t *transitions, *initial;
|
|
tre_tnfa_t *tnfa = NULL;
|
|
tre_submatch_data_t *submatch_data;
|
|
tre_tag_direction_t *tag_directions = NULL;
|
|
reg_errcode_t errcode;
|
|
tre_mem_t mem;
|
|
int numpos = 0;
|
|
|
|
/* Parse context. */
|
|
tre_parse_ctx_t parse_ctx;
|
|
|
|
/* Allocate a stack used throughout the compilation process for various
|
|
purposes. */
|
|
stack = tre_stack_new(512, TRE_MAX_STACK);
|
|
if (!stack)
|
|
return REG_ESPACE;
|
|
/* Allocate a fast memory allocator. */
|
|
mem = tre_mem_new();
|
|
if (!mem)
|
|
{
|
|
tre_stack_destroy(stack);
|
|
return REG_ESPACE;
|
|
}
|
|
|
|
/* Parse the regexp. */
|
|
memset(&parse_ctx, 0, sizeof(parse_ctx));
|
|
parse_ctx.mem = mem;
|
|
parse_ctx.stack = stack;
|
|
parse_ctx.re = regex;
|
|
parse_ctx.len = n;
|
|
parse_ctx.cflags = cflags;
|
|
parse_ctx.max_backref = -1;
|
|
/* Use 8-bit optimizations in 8-bit mode */
|
|
parse_ctx.mb_cur_max = (cflags & REG_USEBYTES) ? 1 : TRE_MB_CUR_MAX;
|
|
DPRINT(("tre_compile: parsing '%.*" STRF "'\n", (int)n, regex));
|
|
errcode = tre_parse(&parse_ctx);
|
|
if (errcode != REG_OK)
|
|
ERROR_EXIT(errcode);
|
|
preg->re_nsub = parse_ctx.submatch_id - 1;
|
|
tree = parse_ctx.result;
|
|
|
|
/* Back references and approximate matching cannot currently be used
|
|
in the same regexp. */
|
|
if (parse_ctx.max_backref >= 0 && parse_ctx.have_approx)
|
|
ERROR_EXIT(REG_BADPAT);
|
|
|
|
#ifdef TRE_DEBUG
|
|
tre_ast_print(tree);
|
|
#endif /* TRE_DEBUG */
|
|
|
|
/* Referring to nonexistent subexpressions is illegal. */
|
|
if (parse_ctx.max_backref > (int)preg->re_nsub)
|
|
ERROR_EXIT(REG_ESUBREG);
|
|
|
|
/* Allocate the TNFA struct. */
|
|
tnfa = xcalloc(1, sizeof(tre_tnfa_t));
|
|
if (tnfa == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
tnfa->have_backrefs = parse_ctx.max_backref >= 0;
|
|
tnfa->have_approx = parse_ctx.have_approx;
|
|
tnfa->num_submatches = parse_ctx.submatch_id;
|
|
|
|
/* The literal optimizer only looks at the final tree plus the outer
|
|
* compile flags. If the regexp changes flags inline with (?i:...) or
|
|
* (?-i:...), those scopes are no longer explicit in the optimized form,
|
|
* so keep using the full matcher. */
|
|
if (!parse_ctx.have_inline_cflags)
|
|
{
|
|
errcode = tre_litopt_try_compile(tnfa, tree, cflags,
|
|
parse_ctx.mb_cur_max);
|
|
if (errcode != REG_OK)
|
|
ERROR_EXIT(errcode);
|
|
}
|
|
|
|
/* Set up tags for submatch addressing. If REG_NOSUB is set and the
|
|
regexp does not have back references, this can be skipped. */
|
|
if (tnfa->have_backrefs || !(cflags & REG_NOSUB))
|
|
{
|
|
DPRINT(("tre_compile: setting up tags\n"));
|
|
|
|
/* Figure out how many tags we will need. */
|
|
errcode = tre_add_tags(NULL, stack, tree, tnfa);
|
|
if (errcode != REG_OK)
|
|
ERROR_EXIT(errcode);
|
|
#ifdef TRE_DEBUG
|
|
tre_ast_print(tree);
|
|
#endif /* TRE_DEBUG */
|
|
|
|
if (tnfa->num_tags > 0)
|
|
{
|
|
tag_directions = xmalloc(sizeof(*tag_directions)
|
|
* (tnfa->num_tags + 1));
|
|
if (tag_directions == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
tnfa->tag_directions = tag_directions;
|
|
memset(tag_directions, -1,
|
|
sizeof(*tag_directions) * (tnfa->num_tags + 1));
|
|
}
|
|
tnfa->minimal_tags = xcalloc(tnfa->num_tags * 2 + 1,
|
|
sizeof(*tnfa->minimal_tags));
|
|
if (tnfa->minimal_tags == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
|
|
submatch_data = xcalloc((unsigned)parse_ctx.submatch_id,
|
|
sizeof(*submatch_data));
|
|
if (submatch_data == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
tnfa->submatch_data = submatch_data;
|
|
|
|
errcode = tre_add_tags(mem, stack, tree, tnfa);
|
|
if (errcode != REG_OK)
|
|
ERROR_EXIT(errcode);
|
|
|
|
#ifdef TRE_DEBUG
|
|
for (i = 0; i < parse_ctx.submatch_id; i++)
|
|
DPRINT(("pmatch[%d] = {t%d, t%d}\n",
|
|
i, submatch_data[i].so_tag, submatch_data[i].eo_tag));
|
|
for (i = 0; i < tnfa->num_tags; i++)
|
|
DPRINT(("t%d is %s\n", i,
|
|
tag_directions[i] == TRE_TAG_MINIMIZE ?
|
|
"minimized" : "maximized"));
|
|
#endif /* TRE_DEBUG */
|
|
}
|
|
|
|
/* Expand iteration nodes. */
|
|
errcode = tre_expand_ast(mem, stack, tree, tag_directions,
|
|
&tnfa->params_depth);
|
|
if (errcode != REG_OK)
|
|
ERROR_EXIT(errcode);
|
|
|
|
/* Add a dummy node for the final state.
|
|
XXX - For certain patterns this dummy node can be optimized away,
|
|
for example "a*" or "ab*". Figure out a simple way to detect
|
|
this possibility. */
|
|
tmp_ast_l = tree;
|
|
tmp_ast_r = tre_ast_new_literal(mem, 0, 0);
|
|
if (tmp_ast_r == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
|
|
tree = tre_ast_new_catenation(mem, tmp_ast_l, tmp_ast_r);
|
|
if (tree == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
|
|
errcode = tre_compute_npfl(mem, stack, tree, &numpos);
|
|
if (errcode != REG_OK)
|
|
ERROR_EXIT(errcode);
|
|
|
|
#ifdef TRE_DEBUG
|
|
tre_ast_print(tree);
|
|
DPRINT(("Number of states: %d\n", numpos));
|
|
#endif /* TRE_DEBUG */
|
|
|
|
counts = xmalloc(sizeof(int) * numpos);
|
|
if (counts == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
|
|
offs = xmalloc(sizeof(int) * numpos);
|
|
if (offs == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
|
|
for (i = 0; i < numpos; i++)
|
|
counts[i] = 0;
|
|
tre_ast_to_tnfa(tree, NULL, counts, NULL);
|
|
|
|
add = 0;
|
|
for (i = 0; i < numpos; i++)
|
|
{
|
|
offs[i] = add;
|
|
add += counts[i] + 1;
|
|
counts[i] = 0;
|
|
}
|
|
transitions = xcalloc((unsigned)add + 1, sizeof(*transitions));
|
|
if (transitions == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
tnfa->transitions = transitions;
|
|
tnfa->num_transitions = add;
|
|
|
|
DPRINT(("Converting to TNFA:\n"));
|
|
errcode = tre_ast_to_tnfa(tree, transitions, counts, offs);
|
|
if (errcode != REG_OK)
|
|
ERROR_EXIT(errcode);
|
|
|
|
/* If in eight bit mode, compute a table of characters that can be the
|
|
first character of a match. */
|
|
tnfa->first_char = -1;
|
|
if (parse_ctx.mb_cur_max == 1 && !tmp_ast_l->nullable)
|
|
{
|
|
int count = 0;
|
|
tre_cint_t k;
|
|
DPRINT(("Characters that can start a match:"));
|
|
tnfa->firstpos_chars = xcalloc(256, sizeof(char));
|
|
if (tnfa->firstpos_chars == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
for (p = tree->firstpos; p->position >= 0; p++)
|
|
{
|
|
tre_tnfa_transition_t *j = transitions + offs[p->position];
|
|
while (j->state != NULL)
|
|
{
|
|
for (k = j->code_min; k <= j->code_max && k < 256; k++)
|
|
{
|
|
DPRINT((" %d", k));
|
|
tnfa->firstpos_chars[k] = 1;
|
|
count++;
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
DPRINT(("\n"));
|
|
#define TRE_OPTIMIZE_FIRST_CHAR 1
|
|
#if TRE_OPTIMIZE_FIRST_CHAR
|
|
if (count == 1)
|
|
{
|
|
for (k = 0; k < 256; k++)
|
|
if (tnfa->firstpos_chars[k])
|
|
{
|
|
DPRINT(("first char must be %d\n", k));
|
|
tnfa->first_char = k;
|
|
xfree(tnfa->firstpos_chars);
|
|
tnfa->firstpos_chars = NULL;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
else
|
|
tnfa->firstpos_chars = NULL;
|
|
|
|
|
|
p = tree->firstpos;
|
|
i = 0;
|
|
while (p->position >= 0)
|
|
{
|
|
i++;
|
|
|
|
#ifdef TRE_DEBUG
|
|
{
|
|
int *tags;
|
|
DPRINT(("initial: %d", p->position));
|
|
tags = p->tags;
|
|
if (tags != NULL)
|
|
{
|
|
if (*tags >= 0)
|
|
DPRINT(("/"));
|
|
while (*tags >= 0)
|
|
{
|
|
DPRINT(("%d", *tags));
|
|
tags++;
|
|
if (*tags >= 0)
|
|
DPRINT((","));
|
|
}
|
|
}
|
|
DPRINT((", assert %d", p->assertions));
|
|
if (p->params)
|
|
{
|
|
DPRINT((", "));
|
|
tre_print_params(p->params);
|
|
}
|
|
DPRINT(("\n"));
|
|
}
|
|
#endif /* TRE_DEBUG */
|
|
|
|
p++;
|
|
}
|
|
|
|
initial = xcalloc((unsigned)i + 1, sizeof(tre_tnfa_transition_t));
|
|
if (initial == NULL)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
tnfa->initial = initial;
|
|
|
|
i = 0;
|
|
for (p = tree->firstpos; p->position >= 0; p++)
|
|
{
|
|
initial[i].state = transitions + offs[p->position];
|
|
initial[i].state_id = p->position;
|
|
initial[i].tags = NULL;
|
|
/* Copy the arrays p->tags, and p->params, they are allocated
|
|
from a tre_mem object. */
|
|
if (p->tags)
|
|
{
|
|
int j;
|
|
for (j = 0; p->tags[j] >= 0; j++);
|
|
initial[i].tags = xmalloc(sizeof(*p->tags) * (j + 1));
|
|
if (!initial[i].tags)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
memcpy(initial[i].tags, p->tags, sizeof(*p->tags) * (j + 1));
|
|
}
|
|
initial[i].params = NULL;
|
|
if (p->params)
|
|
{
|
|
initial[i].params = xmalloc(sizeof(*p->params) * TRE_PARAM_LAST);
|
|
if (!initial[i].params)
|
|
ERROR_EXIT(REG_ESPACE);
|
|
memcpy(initial[i].params, p->params,
|
|
sizeof(*p->params) * TRE_PARAM_LAST);
|
|
}
|
|
initial[i].assertions = p->assertions;
|
|
i++;
|
|
}
|
|
initial[i].state = NULL;
|
|
|
|
tnfa->num_transitions = add;
|
|
tnfa->final = transitions + offs[tree->lastpos[0].position];
|
|
tnfa->num_states = numpos;
|
|
tnfa->cflags = cflags;
|
|
|
|
DPRINT(("final state %p\n", (void *)tnfa->final));
|
|
|
|
tre_mem_destroy(mem);
|
|
tre_stack_destroy(stack);
|
|
xfree(counts);
|
|
xfree(offs);
|
|
|
|
preg->TRE_REGEX_T_FIELD = (void *)tnfa;
|
|
return REG_OK;
|
|
|
|
error_exit:
|
|
/* Free everything that was allocated and return the error code. */
|
|
tre_mem_destroy(mem);
|
|
if (stack != NULL)
|
|
tre_stack_destroy(stack);
|
|
if (counts != NULL)
|
|
xfree(counts);
|
|
if (offs != NULL)
|
|
xfree(offs);
|
|
preg->TRE_REGEX_T_FIELD = (void *)tnfa;
|
|
tre_free(preg);
|
|
return errcode;
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
tre_free(regex_t *preg)
|
|
{
|
|
tre_tnfa_t *tnfa;
|
|
unsigned int i;
|
|
tre_tnfa_transition_t *trans;
|
|
|
|
tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
|
if (!tnfa)
|
|
return;
|
|
|
|
for (i = 0; i < tnfa->num_transitions; i++)
|
|
if (tnfa->transitions[i].state)
|
|
{
|
|
if (tnfa->transitions[i].tags)
|
|
xfree(tnfa->transitions[i].tags);
|
|
if (tnfa->transitions[i].neg_classes)
|
|
xfree(tnfa->transitions[i].neg_classes);
|
|
if (tnfa->transitions[i].params)
|
|
xfree(tnfa->transitions[i].params);
|
|
}
|
|
if (tnfa->transitions)
|
|
xfree(tnfa->transitions);
|
|
|
|
if (tnfa->initial)
|
|
{
|
|
for (trans = tnfa->initial; trans->state; trans++)
|
|
{
|
|
if (trans->tags)
|
|
xfree(trans->tags);
|
|
if (trans->params)
|
|
xfree(trans->params);
|
|
}
|
|
xfree(tnfa->initial);
|
|
}
|
|
|
|
if (tnfa->submatch_data)
|
|
{
|
|
for (i = 0; i < tnfa->num_submatches; i++)
|
|
if (tnfa->submatch_data[i].parents)
|
|
xfree(tnfa->submatch_data[i].parents);
|
|
xfree(tnfa->submatch_data);
|
|
}
|
|
|
|
if (tnfa->tag_directions)
|
|
xfree(tnfa->tag_directions);
|
|
if (tnfa->firstpos_chars)
|
|
xfree(tnfa->firstpos_chars);
|
|
if (tnfa->minimal_tags)
|
|
xfree(tnfa->minimal_tags);
|
|
tre_litopt_free_literal_list(tnfa->literal_opt.literals,
|
|
tnfa->literal_opt.num_literals);
|
|
xfree(tnfa);
|
|
}
|
|
|
|
char *
|
|
tre_version(void)
|
|
{
|
|
static char str[256];
|
|
char *version;
|
|
|
|
if (str[0] == 0)
|
|
{
|
|
(void) tre_config(TRE_CONFIG_VERSION, &version);
|
|
(void) snprintf(str, sizeof(str), "TRE %s (BSD)", version);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
int
|
|
tre_config(int query, void *result)
|
|
{
|
|
int *int_result = result;
|
|
const char **string_result = result;
|
|
|
|
switch (query)
|
|
{
|
|
case TRE_CONFIG_APPROX:
|
|
#ifdef TRE_APPROX
|
|
*int_result = 1;
|
|
#else /* !TRE_APPROX */
|
|
*int_result = 0;
|
|
#endif /* !TRE_APPROX */
|
|
return REG_OK;
|
|
|
|
case TRE_CONFIG_WCHAR:
|
|
#ifdef TRE_WCHAR
|
|
*int_result = 1;
|
|
#else /* !TRE_WCHAR */
|
|
*int_result = 0;
|
|
#endif /* !TRE_WCHAR */
|
|
return REG_OK;
|
|
|
|
case TRE_CONFIG_MULTIBYTE:
|
|
#ifdef TRE_MULTIBYTE
|
|
*int_result = 1;
|
|
#else /* !TRE_MULTIBYTE */
|
|
*int_result = 0;
|
|
#endif /* !TRE_MULTIBYTE */
|
|
return REG_OK;
|
|
|
|
case TRE_CONFIG_SYSTEM_ABI:
|
|
#ifdef TRE_CONFIG_SYSTEM_ABI
|
|
*int_result = 1;
|
|
#else /* !TRE_CONFIG_SYSTEM_ABI */
|
|
*int_result = 0;
|
|
#endif /* !TRE_CONFIG_SYSTEM_ABI */
|
|
return REG_OK;
|
|
|
|
case TRE_CONFIG_VERSION:
|
|
*string_result = TRE_VERSION;
|
|
return REG_OK;
|
|
}
|
|
|
|
return REG_NOMATCH;
|
|
}
|
|
|
|
|
|
/* EOF */
|