Rev 81629 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/*tre-match-approx.c - TRE approximate regex matching engineThis software is released under a BSD-style license.See the file LICENSE for details and copyright.*/#ifdef HAVE_CONFIG_H#include <config.h>#endif /* HAVE_CONFIG_H *//* AIX requires this to be the first thing in the file. */#ifdef TRE_USE_ALLOCA#ifndef __GNUC__# if HAVE_ALLOCA_H# include <alloca.h># else# ifdef _AIX#pragma alloca# else# ifndef alloca /* predefined by HP cc +Olibcalls */char *alloca ();# endif# endif# endif#endif#endif /* TRE_USE_ALLOCA *//* These seem compiler/OS-specific, but unexplainedOn Linux the first is intended to be used only with GCC.#define __USE_STRING_INLINES#undef __NO_INLINE__*/// #include <assert.h>#include <stdlib.h>#include <string.h>#include <limits.h>#ifdef HAVE_WCHAR_H#include <wchar.h>#endif /* HAVE_WCHAR_H */#ifdef HAVE_WCTYPE_H#include <wctype.h>#endif /* HAVE_WCTYPE_H */#ifndef TRE_WCHAR#include <ctype.h>#endif /* !TRE_WCHAR */#ifdef HAVE_MALLOC_H#include <malloc.h>#endif /* HAVE_MALLOC_H */#include "tre-internal.h"#include "tre-match-utils.h"#include "tre.h"#include "xmalloc.h"#define assert(a) R_assert(a)#define TRE_M_COST 0#define TRE_M_NUM_INS 1#define TRE_M_NUM_DEL 2#define TRE_M_NUM_SUBST 3#define TRE_M_NUM_ERR 4#define TRE_M_LAST 5#define TRE_M_MAX_DEPTH 3typedef struct {/* State in the TNFA transition table. */tre_tnfa_transition_t *state;/* Position in input string. */int pos;/* Tag values. */int *tags;/* Matching parameters. */regaparams_t params;/* Nesting depth of parameters. This is used as an index inthe `costs' array. */int depth;/* Costs and counter values for different parameter nesting depths. */int costs[TRE_M_MAX_DEPTH + 1][TRE_M_LAST];} tre_tnfa_approx_reach_t;#ifdef TRE_DEBUG/* Prints the `reach' array in a readable fashion with DPRINT. */static voidtre_print_reach(const tre_tnfa_t *tnfa, tre_tnfa_approx_reach_t *reach,int pos, int num_tags){int id;/* Print each state on one line. */DPRINT((" reach:\n"));for (id = 0; id < tnfa->num_states; id++){int i, j;if (reach[id].pos < pos)continue; /* Not reached. */DPRINT((" %03d, costs ", id));for (i = 0; i <= reach[id].depth; i++){DPRINT(("["));for (j = 0; j < TRE_M_LAST; j++){DPRINT(("%2d", reach[id].costs[i][j]));if (j + 1 < TRE_M_LAST)DPRINT((","));}DPRINT(("]"));if (i + 1 <= reach[id].depth)DPRINT((", "));}DPRINT(("\n tags "));for (i = 0; i < num_tags; i++){DPRINT(("%02d", reach[id].tags[i]));if (i + 1 < num_tags)DPRINT((","));}DPRINT(("\n"));}DPRINT(("\n"));}#endif /* TRE_DEBUG *//* Sets the matching parameters in `reach' to the ones defined in the `pa'array. If `pa' specifies default values, they are taken from`default_params'. */inline static voidtre_set_params(tre_tnfa_approx_reach_t *reach,int *pa, regaparams_t default_params){int value;/* If depth is increased reset costs and counters to zero for thenew levels. */value = pa[TRE_PARAM_DEPTH];assert(value <= TRE_M_MAX_DEPTH);if (value > reach->depth){int i, j;for (i = reach->depth + 1; i <= value; i++)for (j = 0; j < TRE_M_LAST; j++)reach->costs[i][j] = 0;}reach->depth = value;/* Set insert cost. */value = pa[TRE_PARAM_COST_INS];if (value == TRE_PARAM_DEFAULT)reach->params.cost_ins = default_params.cost_ins;else if (value != TRE_PARAM_UNSET)reach->params.cost_ins = value;/* Set delete cost. */value = pa[TRE_PARAM_COST_DEL];if (value == TRE_PARAM_DEFAULT)reach->params.cost_del = default_params.cost_del;else if (value != TRE_PARAM_UNSET)reach->params.cost_del = value;/* Set substitute cost. */value = pa[TRE_PARAM_COST_SUBST];if (value == TRE_PARAM_DEFAULT)reach->params.cost_subst = default_params.cost_subst;elsereach->params.cost_subst = value;/* Set maximum cost. */value = pa[TRE_PARAM_COST_MAX];if (value == TRE_PARAM_DEFAULT)reach->params.max_cost = default_params.max_cost;else if (value != TRE_PARAM_UNSET)reach->params.max_cost = value;/* Set maximum inserts. */value = pa[TRE_PARAM_MAX_INS];if (value == TRE_PARAM_DEFAULT)reach->params.max_ins = default_params.max_ins;else if (value != TRE_PARAM_UNSET)reach->params.max_ins = value;/* Set maximum deletes. */value = pa[TRE_PARAM_MAX_DEL];if (value == TRE_PARAM_DEFAULT)reach->params.max_del = default_params.max_del;else if (value != TRE_PARAM_UNSET)reach->params.max_del = value;/* Set maximum substitutes. */value = pa[TRE_PARAM_MAX_SUBST];if (value == TRE_PARAM_DEFAULT)reach->params.max_subst = default_params.max_subst;else if (value != TRE_PARAM_UNSET)reach->params.max_subst = value;/* Set maximum number of errors. */value = pa[TRE_PARAM_MAX_ERR];if (value == TRE_PARAM_DEFAULT)reach->params.max_err = default_params.max_err;else if (value != TRE_PARAM_UNSET)reach->params.max_err = value;}reg_errcode_ttre_tnfa_run_approx(const tre_tnfa_t *tnfa, const void *string, int len,tre_str_type_t type, int *match_tags,regamatch_t *match, regaparams_t default_params,int eflags, int *match_end_ofs){/* State variables required by GET_NEXT_WCHAR. */tre_char_t prev_c = 0, next_c = 0;const char *str_byte = string;int pos = -1;unsigned int pos_add_next = 1;#ifdef TRE_WCHARconst wchar_t *str_wide = string;#ifdef TRE_MBSTATEmbstate_t mbstate;#endif /* !TRE_WCHAR */#endif /* TRE_WCHAR */int reg_notbol = eflags & REG_NOTBOL;int reg_noteol = eflags & REG_NOTEOL;int reg_newline = tnfa->cflags & REG_NEWLINE;int str_user_end = 0;int prev_pos;/* Number of tags. */int num_tags;/* The reach tables. */tre_tnfa_approx_reach_t *reach, *reach_next;/* Tag array for temporary use. */int *tmp_tags;/* End offset of best match so far, or -1 if no match found yet. */int match_eo = -1;/* Costs of the match. */int match_costs[TRE_M_LAST];/* Space for temporary data required for matching. */unsigned char *buf;int i, id;if (!match_tags)num_tags = 0;elsenum_tags = tnfa->num_tags;#ifdef TRE_MBSTATEmemset(&mbstate, '\0', sizeof(mbstate));#endif /* TRE_MBSTATE */DPRINT(("tre_tnfa_run_approx, input type %d, len %d, eflags %d, ""match_tags %p\n",type, len, eflags,match_tags));DPRINT(("max cost %d, ins %d, del %d, subst %d\n",default_params.max_cost,default_params.cost_ins,default_params.cost_del,default_params.cost_subst));/* Allocate memory for temporary data required for matching. This needs tobe done for every matching operation to be thread safe. This allocateseverything in a single large block from the stack frame using alloca()or with malloc() if alloca is unavailable. */{unsigned char *buf_cursor;/* Space needed for one array of tags. */int tag_bytes = sizeof(*tmp_tags) * num_tags;/* Space needed for one reach table. */int reach_bytes = sizeof(*reach_next) * tnfa->num_states;/* Total space needed. */int total_bytes = reach_bytes * 2 + (tnfa->num_states * 2 + 1 ) * tag_bytes;/* Add some extra to make sure we can align the pointers. The multiplierused here must be equal to the number of ALIGN calls below. */total_bytes += (sizeof(long) - 1) * 3;/* Allocate the memory. */#ifdef TRE_USE_ALLOCAbuf = alloca(total_bytes);#else /* !TRE_USE_ALLOCA */buf = xmalloc((unsigned)total_bytes);#endif /* !TRE_USE_ALLOCA */if (!buf)return REG_ESPACE;memset(buf, 0, (size_t)total_bytes);/* Allocate `tmp_tags' from `buf'. */tmp_tags = (void *)buf;buf_cursor = buf + tag_bytes;buf_cursor += ALIGN(buf_cursor, long);/* Allocate `reach' from `buf'. */reach = (void *)buf_cursor;buf_cursor += reach_bytes;buf_cursor += ALIGN(buf_cursor, long);/* Allocate `reach_next' from `buf'. */reach_next = (void *)buf_cursor;buf_cursor += reach_bytes;buf_cursor += ALIGN(buf_cursor, long);/* Allocate tag arrays for `reach' and `reach_next' from `buf'. */for (i = 0; i < tnfa->num_states; i++){reach[i].tags = (void *)buf_cursor;buf_cursor += tag_bytes;reach_next[i].tags = (void *)buf_cursor;buf_cursor += tag_bytes;}assert(buf_cursor <= buf + total_bytes);}for (i = 0; i < TRE_M_LAST; i++)match_costs[i] = INT_MAX;/* Mark the reach arrays empty. */for (i = 0; i < tnfa->num_states; i++)reach[i].pos = reach_next[i].pos = -2;prev_pos = pos;GET_NEXT_WCHAR();pos = 0;while (/*CONSTCOND*/(void)1,1){DPRINT(("%03d:%2lc/%05d\n", pos, (tre_cint_t)next_c, (int)next_c));/* Add initial states to `reach_next' if an exact match has not yetbeen found. */if (match_costs[TRE_M_COST] > 0){tre_tnfa_transition_t *trans;DPRINT((" init"));for (trans = tnfa->initial; trans->state; trans++){int stateid = trans->state_id;/* If this state is not currently in `reach_next', add itthere. */if (reach_next[stateid].pos < pos){if (trans->assertions && CHECK_ASSERTIONS(trans->assertions)){/* Assertions failed, don't add this state. */DPRINT((" !%d (assert)", stateid));continue;}DPRINT((" %d", stateid));reach_next[stateid].state = trans->state;reach_next[stateid].pos = pos;/* Compute tag values after this transition. */for (i = 0; i < num_tags; i++)reach_next[stateid].tags[i] = -1;if (trans->tags)for (i = 0; trans->tags[i] >= 0; i++)if (trans->tags[i] < num_tags)reach_next[stateid].tags[trans->tags[i]] = pos;/* Set the parameters, depth, and costs. */reach_next[stateid].params = default_params;reach_next[stateid].depth = 0;for (i = 0; i < TRE_M_LAST; i++)reach_next[stateid].costs[0][i] = 0;if (trans->params)tre_set_params(&reach_next[stateid], trans->params,default_params);/* If this is the final state, mark the exact match. */if (trans->state == tnfa->final){match_eo = pos;for (i = 0; i < num_tags; i++)match_tags[i] = reach_next[stateid].tags[i];for (i = 0; i < TRE_M_LAST; i++)match_costs[i] = 0;}}}DPRINT(("\n"));}/* Handle inserts. This is done by pretending there's an epsilontransition from each state in `reach' back to the same state.We don't need to worry about the final state here; this will nevergive a better match than what we already have. */for (id = 0; id < tnfa->num_states; id++){int depth;int cost, cost0;if (reach[id].pos != prev_pos){DPRINT((" insert: %d not reached\n", id));continue; /* Not reached. */}depth = reach[id].depth;/* Compute and check cost at current depth. */cost = reach[id].costs[depth][TRE_M_COST];if (reach[id].params.cost_ins != TRE_PARAM_UNSET)cost += reach[id].params.cost_ins;if (cost > reach[id].params.max_cost)continue; /* Cost too large. *//* Check number of inserts at current depth. */if (reach[id].costs[depth][TRE_M_NUM_INS] + 1> reach[id].params.max_ins)continue; /* Too many inserts. *//* Check total number of errors at current depth. */if (reach[id].costs[depth][TRE_M_NUM_ERR] + 1> reach[id].params.max_err)continue; /* Too many errors. *//* Compute overall cost. */cost0 = cost;if (depth > 0){cost0 = reach[id].costs[0][TRE_M_COST];if (reach[id].params.cost_ins != TRE_PARAM_UNSET)cost0 += reach[id].params.cost_ins;elsecost0 += default_params.cost_ins;}DPRINT((" insert: from %d to %d, cost %d: ", id, id,reach[id].costs[depth][TRE_M_COST]));if (reach_next[id].pos == pos&& (cost0 >= reach_next[id].costs[0][TRE_M_COST])){DPRINT(("lose\n"));continue;}DPRINT(("win\n"));/* Copy state, position, tags, parameters, and depth. */reach_next[id].state = reach[id].state;reach_next[id].pos = pos;for (i = 0; i < num_tags; i++)reach_next[id].tags[i] = reach[id].tags[i];reach_next[id].params = reach[id].params;reach_next[id].depth = reach[id].depth;/* Set the costs after this transition. */memcpy(reach_next[id].costs, reach[id].costs,sizeof(reach_next[id].costs[0][0])* TRE_M_LAST * (depth + 1));reach_next[id].costs[depth][TRE_M_COST] = cost;reach_next[id].costs[depth][TRE_M_NUM_INS]++;reach_next[id].costs[depth][TRE_M_NUM_ERR]++;if (depth > 0){reach_next[id].costs[0][TRE_M_COST] = cost0;reach_next[id].costs[0][TRE_M_NUM_INS]++;reach_next[id].costs[0][TRE_M_NUM_ERR]++;}}/* Handle deletes. This is done by traversing through the whole TNFApretending that all transitions are epsilon transitions, untilno more states can be reached with better costs. */{int rb_size = 256;tre_tnfa_approx_reach_t *static_ringbuffer[256];tre_tnfa_approx_reach_t **ringbuffer = static_ringbuffer;tre_tnfa_approx_reach_t **deque_start, **deque_end;deque_start = deque_end = ringbuffer;/* Add all states in `reach_next' to the deque. */for (id = 0; id < tnfa->num_states; id++){if (reach_next[id].pos != pos)continue;*deque_end = &reach_next[id];deque_end++;/* check if we need to resize the buffer */if (deque_end >= (ringbuffer + rb_size)) {tre_tnfa_approx_reach_t **larger_buf;rb_size += 512;size_t os = deque_start - ringbuffer,oe = deque_end - ringbuffer;// GCC complained here that ringbuffer is still used.// Although realloc could free it, it does not change the pointer.larger_buf = (tre_tnfa_approx_reach_t **)((ringbuffer == static_ringbuffer) ?xmalloc(sizeof(tre_tnfa_approx_reach_t *) * rb_size) :xrealloc(ringbuffer, sizeof(tre_tnfa_approx_reach_t *) * rb_size));if (!larger_buf) {DPRINT(("tre_tnfa_run_approx: cannot resize ring buffer\n"));if (ringbuffer != static_ringbuffer)xfree(ringbuffer);#ifndef TRE_USE_ALLOCAif (buf)xfree(buf);#endif /* !TRE_USE_ALLOCA */return REG_ESPACE;}deque_start = larger_buf + os;deque_end = larger_buf + oe;if (ringbuffer == static_ringbuffer) /* when switching from stack to heap we need to copy */memcpy(larger_buf, ringbuffer, sizeof(static_ringbuffer));ringbuffer = larger_buf;}}/* Repeat until the deque is empty. */while (deque_end != deque_start){tre_tnfa_approx_reach_t *reach_p;int depth;int cost, cost0;tre_tnfa_transition_t *trans;/* Pop the first item off the deque. */reach_p = *deque_start;id = (int)(reach_p - reach_next);depth = reach_p->depth;/* Compute cost at current depth. */cost = reach_p->costs[depth][TRE_M_COST];if (reach_p->params.cost_del != TRE_PARAM_UNSET)cost += reach_p->params.cost_del;/* Check cost, number of deletes, and total number of errorsat current depth. */if (cost > reach_p->params.max_cost|| (reach_p->costs[depth][TRE_M_NUM_DEL] + 1> reach_p->params.max_del)|| (reach_p->costs[depth][TRE_M_NUM_ERR] + 1> reach_p->params.max_err)){/* Too many errors or cost too large. */DPRINT((" delete: from %03d: cost too large\n", id));deque_start++;if (deque_start >= (ringbuffer + rb_size))deque_start = ringbuffer;continue;}/* Compute overall cost. */cost0 = cost;if (depth > 0){cost0 = reach_p->costs[0][TRE_M_COST];if (reach_p->params.cost_del != TRE_PARAM_UNSET)cost0 += reach_p->params.cost_del;elsecost0 += default_params.cost_del;}for (trans = reach_p->state; trans->state; trans++){int dest_id = trans->state_id;DPRINT((" delete: from %03d to %03d, cost %d (%d): ",id, dest_id, cost0, reach_p->params.max_cost));if (trans->assertions && CHECK_ASSERTIONS(trans->assertions)){DPRINT(("assertion failed\n"));continue;}/* Compute tag values after this transition. */for (i = 0; i < num_tags; i++)tmp_tags[i] = reach_p->tags[i];if (trans->tags)for (i = 0; trans->tags[i] >= 0; i++)if (trans->tags[i] < num_tags)tmp_tags[trans->tags[i]] = pos;/* If another path has also reached this state, choose the onewith the smallest cost or best tags if costs are equal. */if (reach_next[dest_id].pos == pos&& (cost0 > reach_next[dest_id].costs[0][TRE_M_COST]|| (cost0 == reach_next[dest_id].costs[0][TRE_M_COST]&& (!match_tags|| !tre_tag_order(num_tags,tnfa->tag_directions,tmp_tags,reach_next[dest_id].tags))))){DPRINT(("lose, cost0 %d, have %d\n",cost0, reach_next[dest_id].costs[0][TRE_M_COST]));continue;}DPRINT(("win\n"));/* Set state, position, tags, parameters, depth, and costs. */reach_next[dest_id].state = trans->state;reach_next[dest_id].pos = pos;for (i = 0; i < num_tags; i++)reach_next[dest_id].tags[i] = tmp_tags[i];reach_next[dest_id].params = reach_p->params;if (trans->params)tre_set_params(&reach_next[dest_id], trans->params,default_params);reach_next[dest_id].depth = reach_p->depth;memcpy(&reach_next[dest_id].costs,reach_p->costs,sizeof(reach_p->costs[0][0])* TRE_M_LAST * (depth + 1));reach_next[dest_id].costs[depth][TRE_M_COST] = cost;reach_next[dest_id].costs[depth][TRE_M_NUM_DEL]++;reach_next[dest_id].costs[depth][TRE_M_NUM_ERR]++;if (depth > 0){reach_next[dest_id].costs[0][TRE_M_COST] = cost0;reach_next[dest_id].costs[0][TRE_M_NUM_DEL]++;reach_next[dest_id].costs[0][TRE_M_NUM_ERR]++;}if (trans->state == tnfa->final&& (match_eo < 0|| match_costs[TRE_M_COST] > cost0|| (match_costs[TRE_M_COST] == cost0&& (num_tags > 0&& tmp_tags[0] <= match_tags[0])))){DPRINT((" setting new match at %d, cost %d\n",pos, cost0));match_eo = pos;memcpy(match_costs, reach_next[dest_id].costs[0],sizeof(match_costs[0]) * TRE_M_LAST);for (i = 0; i < num_tags; i++)match_tags[i] = tmp_tags[i];}/* Add to the end of the deque. */*deque_end = &reach_next[dest_id];deque_end++;if (deque_end >= (ringbuffer + rb_size))deque_end = ringbuffer;assert(deque_end != deque_start);}deque_start++;if (deque_start >= (ringbuffer + rb_size))deque_start = ringbuffer;}if (ringbuffer != static_ringbuffer)xfree(ringbuffer);}#ifdef TRE_DEBUGtre_print_reach(tnfa, reach_next, pos, num_tags);#endif /* TRE_DEBUG *//* Check for end of string. */if (len < 0){if (type == STR_USER){if (str_user_end)break;}else if (next_c == L'\0')break;}else{if (pos >= len)break;}prev_pos = pos;GET_NEXT_WCHAR();/* Swap `reach' and `reach_next'. */{tre_tnfa_approx_reach_t *tmp;tmp = reach;reach = reach_next;reach_next = tmp;}/* Handle exact matches and substitutions. */for (id = 0; id < tnfa->num_states; id++){tre_tnfa_transition_t *trans;if (reach[id].pos < prev_pos)continue; /* Not reached. */for (trans = reach[id].state; trans->state; trans++){int dest_id;int depth;int cost, cost0, err;if (trans->assertions&& (CHECK_ASSERTIONS(trans->assertions)|| CHECK_CHAR_CLASSES(trans, tnfa, eflags))){DPRINT((" exact, from %d: assert failed\n", id));continue;}depth = reach[id].depth;dest_id = trans->state_id;cost = reach[id].costs[depth][TRE_M_COST];cost0 = reach[id].costs[0][TRE_M_COST];err = 0;if (trans->code_min > (tre_cint_t)prev_c|| trans->code_max < (tre_cint_t)prev_c){/* Handle substitutes. The required character was not inthe string, so match it in place of whatever was supposedto be there and increase costs accordingly. */err = 1;/* Compute and check cost at current depth. */cost = reach[id].costs[depth][TRE_M_COST];if (reach[id].params.cost_subst != TRE_PARAM_UNSET)cost += reach[id].params.cost_subst;if (cost > reach[id].params.max_cost)continue; /* Cost too large. *//* Check number of substitutes at current depth. */if (reach[id].costs[depth][TRE_M_NUM_SUBST] + 1> reach[id].params.max_subst)continue; /* Too many substitutes. *//* Check total number of errors at current depth. */if (reach[id].costs[depth][TRE_M_NUM_ERR] + 1> reach[id].params.max_err)continue; /* Too many errors. *//* Compute overall cost. */cost0 = cost;if (depth > 0){cost0 = reach[id].costs[0][TRE_M_COST];if (reach[id].params.cost_subst != TRE_PARAM_UNSET)cost0 += reach[id].params.cost_subst;elsecost0 += default_params.cost_subst;}DPRINT((" subst, from %03d to %03d, cost %d: ",id, dest_id, cost0));}elseDPRINT((" exact, from %03d to %03d, cost %d: ",id, dest_id, cost0));/* Compute tag values after this transition. */for (i = 0; i < num_tags; i++)tmp_tags[i] = reach[id].tags[i];if (trans->tags)for (i = 0; trans->tags[i] >= 0; i++)if (trans->tags[i] < num_tags)tmp_tags[trans->tags[i]] = pos;/* If another path has also reached this state, choose theone with the smallest cost or best tags if costs are equal. */if (reach_next[dest_id].pos == pos&& (cost0 > reach_next[dest_id].costs[0][TRE_M_COST]|| (cost0 == reach_next[dest_id].costs[0][TRE_M_COST]&& !tre_tag_order(num_tags, tnfa->tag_directions,tmp_tags,reach_next[dest_id].tags)))){DPRINT(("lose\n"));continue;}DPRINT(("win %d %d\n",reach_next[dest_id].pos,reach_next[dest_id].costs[0][TRE_M_COST]));/* Set state, position, tags, and depth. */reach_next[dest_id].state = trans->state;reach_next[dest_id].pos = pos;for (i = 0; i < num_tags; i++)reach_next[dest_id].tags[i] = tmp_tags[i];reach_next[dest_id].depth = reach[id].depth;/* Set parameters. */reach_next[dest_id].params = reach[id].params;if (trans->params)tre_set_params(&reach_next[dest_id], trans->params,default_params);/* Set the costs after this transition. */memcpy(&reach_next[dest_id].costs,reach[id].costs,sizeof(reach[id].costs[0][0])* TRE_M_LAST * (depth + 1));reach_next[dest_id].costs[depth][TRE_M_COST] = cost;reach_next[dest_id].costs[depth][TRE_M_NUM_SUBST] += err;reach_next[dest_id].costs[depth][TRE_M_NUM_ERR] += err;if (depth > 0){reach_next[dest_id].costs[0][TRE_M_COST] = cost0;reach_next[dest_id].costs[0][TRE_M_NUM_SUBST] += err;reach_next[dest_id].costs[0][TRE_M_NUM_ERR] += err;}if (trans->state == tnfa->final&& (match_eo < 0|| cost0 < match_costs[TRE_M_COST]|| (cost0 == match_costs[TRE_M_COST]&& num_tags > 0 && tmp_tags[0] <= match_tags[0]))){DPRINT((" setting new match at %d, cost %d\n",pos, cost0));match_eo = pos;for (i = 0; i < TRE_M_LAST; i++)match_costs[i] = reach_next[dest_id].costs[0][i];for (i = 0; i < num_tags; i++)match_tags[i] = tmp_tags[i];}}}}DPRINT(("match end offset = %d, match cost = %d\n", match_eo,match_costs[TRE_M_COST]));#ifndef TRE_USE_ALLOCAif (buf)xfree(buf);#endif /* !TRE_USE_ALLOCA */match->cost = match_costs[TRE_M_COST];match->num_ins = match_costs[TRE_M_NUM_INS];match->num_del = match_costs[TRE_M_NUM_DEL];match->num_subst = match_costs[TRE_M_NUM_SUBST];*match_end_ofs = match_eo;return match_eo >= 0 ? REG_OK : REG_NOMATCH;}