/* dfa.c - functions that manipulate regexps as DFAs * **************************************************************** * Copyright (C) 1998, 2000 Thomas Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "hackerlab/bugs/panic.h" #include "hackerlab/mem/mem.h" #include "hackerlab/rx/escape.h" #include "hackerlab/rx/dfa.h" /************************************************************************ *(h0 "DFA String Comparisons" * :includes ("rx/dfa.h")) * * * A common use for regular expressions is to compile them to DFA and * compare them to strings using a loop that advances through DFA * states. * * In Rx, the DFA data structure has been heavily optimized for such * loops. The functions in this chapter implement the most common * kinds of DFA loop, taking full advantage of the Rx optimizations. */ /*(menu) */ /*(include-documentation "dfa.h") */ /************************************************************************ *(h1 "DFA Allocation Functions") * */ /*(c rx_dfa_alloc) * struct rx_dfa * rx_dfa_alloc (alloc_limits limits); * * Allocate a new DFA, initially not pointing to any NFA or DFA state. * */ struct rx_dfa * rx_dfa_alloc (alloc_limits limits) { struct rx_dfa * a; a = (struct rx_dfa *)lim_malloc (limits, sizeof (*a)); mem_set0 ((t_uchar *)a, sizeof (*a)); return a; } /*(c rx_dfa_free) * void rx_free_dfa (alloc_limits limits, struct rx_dfa * dfa); * * Release all storage associated with `dfa'. */ void rx_dfa_free (alloc_limits limits, struct rx_dfa * dfa) { if (dfa->rx) rx_clear_dfa_state (dfa); lim_free (limits, dfa); } /************************************************************************ *(h1 "Initializing a DFA") * */ /*(c rx_init_dfa_from_nfa) * void rx_init_dfa_from_nfa (struct rx_dfa * frame, struct rx_nfa * rx); * * Make `rx' the NFA of DFA machine `frame'. * * The DFA machine must not already have a current DFA state (must be * in state 0). See xref:"rx_clear_dfa_state". * * This function sets the DFA used by `frame', but does not set the * current DFA state of `frame'. */ void rx_init_dfa_from_nfa (struct rx_dfa * frame, struct rx_nfa * rx) { frame->rx = rx; frame->state = 0; frame->final_tag = 0; } /*(c rx_init_dfa_from_dfa) * void rx_init_dfa_from_dfa (struct rx_dfa * dest, struct rx_dfa * src); * * Make the NFA and DFA state of `src' the NFA and DFA state of * `dest'. After a call to this function (which is inexpensive), you * have two DFA machines in equivalent states. * * The DFA machine `dest' must not already have a current DFA state * (`state' must be in state 0). See xref:"rx_clear_dfa_state". */ void rx_init_dfa_from_dfa (struct rx_dfa * dest, struct rx_dfa * src) { dest->rx = src->rx; dest->state = src->state; dest->final_tag = src->final_tag; if (dest->state) rx_lock_superstate (dest->rx, dest->state); } /*(c rx_clear_dfa_state) * void rx_clear_dfa_state (struct rx_dfa * frame); * * Clear the DFA state of DFA machine `frame'. */ void rx_clear_dfa_state (struct rx_dfa * frame) { if (frame->state) { rx_unlock_superstate (frame->rx, frame->state); frame->state = 0; frame->final_tag = 0; } } /* rx_dfa_goto_start_superstate * * Return (or initialize) a DFA to its start state. */ /*(c rx_dfa_goto_start_superstate) * void rx_dfa_goto_start_superstate (struct rx_dfa * frame, * int storage_unit_size); * * Return state machine `frame' to its starting state. * * The starting state is a DFA state built from the epsilon closure of * the starting state of the NFA. See xref:"rx_set_start_state". */ int rx_dfa_goto_start_superstate (struct rx_dfa * frame, int storage_unit_size) { struct rx_superstate * start_state; start_state = rx_nfa_state_to_superstate (frame->rx, frame->rx->start_nfa_state, storage_unit_size); if (!start_state) return -1; if (frame->state) rx_unlock_superstate (frame->rx, frame->state); frame->state = start_state; frame->final_tag = start_state->members->state_label; rx_lock_superstate (frame->rx, frame->state); return 0; } /************************************************************************ *(h1 "DFA Comparison Functions") * * The functions in this section compare an input string to a regular * expression by advancing through DFA states according to the * characters in the input string. */ /*(c rx_dfa_can_continue) * int rx_dfa_can_continue (struct rx_dfa * frame); * * Return a non-zero value if there exist characters for which the * current state of DFA machine `frame' has transitions defined. If * this function returns 0, then the machine is in a dead-end state. */ int rx_dfa_can_continue (struct rx_dfa * frame) { return ( frame->state && frame->state->members->has_cset_edges); } int rx_dfa_tag (struct rx_dfa * frame) { return frame->final_tag; } /*(c rx_dfa_fits) * int rx_dfa_fits (int * label, * struct rx_dfa * frame, * const t_uchar * burst, * size_t len); * * * Compare a DFA to string: is the entire string matched by the DFA? * Return -1 if an error occurs, 0 otherwise. * * The final state label reached is returned in `*label': 0 if * if the string does not match, non-0 if it does. * * It is possible to asynchronously abort a call to this function. * See xref:"Exiting Long-running Matches". */ int rx_dfa_fits (int * label, struct rx_dfa * frame, const t_uchar * burst, size_t len) { int adv; adv = rx_dfa_advance (frame, burst, len); if (adv < 0) return -1; else if (!adv) { *label = 0; return 0; } else { *label = frame->final_tag; return 0; } } /*(c rx_dfa_advance) * int rx_dfa_advance (struct rx_dfa * frame, * const t_uchar * burst, * size_t len); * * * Advance a DFA, reading characters from the input string. Stop at * the end of the string, returning 1 or when a character is * encountered for which no transition is defined, returning 0. * -1 == ESPACE. * * This is similar to `rx_dfa_fits', except that in this case, we * don't care about the state label of the final state. * * It is possible to asynchronously abort a call to this function. * See xref:"Exiting Long-running Matches". */ int rx_dfa_advance (struct rx_dfa * frame, const t_uchar * burst, size_t len) { rx_transition_table inx_table; if (!len) return 1; inx_table = frame->state->transitions; rx_unlock_superstate (frame->rx, frame->state); frame->state = 0; while (len--) { struct rx_inx * inx; rx_transition_table next_table; if (rx_poll) (*rx_poll)(); inx = rx_transition8 (inx_table, *burst); next_table = (rx_transition_table)inx->data; while (!next_table) { struct rx_superstate * state; state = rx_transitions_to_suprestate (inx_table); switch ((long)inx->inx) { case rx_backtrack: /* RX_BACKTRACK means that we've reached the empty * superstate, indicating that match can't succeed * from this point. */ frame->state = 0; frame->final_tag = 0; return 0; case rx_cache_miss: /* Because the superstate NFA is lazily constructed, * and in fact may erode from underneath us, we sometimes * have to construct the next instruction from the hard way. * This invokes one step in the lazy-conversion. */ inx = rx_handle_cache_miss (frame->rx, state, *burst, inx->data_2); if (!inx) { frame->state = 0; frame->final_tag = 0; return -1; } next_table = (rx_transition_table)inx->data; continue; /* No other instructions are legal here. */ default: panic ("unrecognized instruction in rx_dfa_advance"); } } inx_table = next_table; ++burst; } frame->state = rx_transitions_to_suprestate (inx_table); frame->final_tag = frame->state->members->state_label; rx_lock_superstate (frame->rx, frame->state); return 1; } /*(c rx_dfa_advance_to_final) * size_t rx_dfa_advance_to_final (struct rx_dfa * frame, * const t_uchar * burst, * size_t len); * * Advance a DFA, reading characters from a string. * * Stop at the end of the string, a character with no transition, or * when a superstate is encountered with a non-0 label. Return the * number of characters read from the string. * * 0 == backtrack, 1 == found final state or ran out of characters, -1 == ESPACE * * This function stops on a transition *into* a state with a non-0 * state label. It doesn't matter if the machine is initially in a * state with a non-0 label: the machine will consume the first input * character regardless. That means that if your regular expression * can match the empty string, you must detect this condition before * calling `rx_dfa_advance_to_final' by checking `dfa->final_tag' * after setting the start state of the DFA. * * If the match stopped in a final state, `dfa->final_tag' contains * the non-0 state label of the final state, otherwise, it contains 0. * If the match stopped on an illegal character, `dfa->state' is 0, * otherwise it is non-0. * * It is possible to asynchronously abort a call to this function. * See xref:"Exiting Long-running Matches". */ int rx_dfa_advance_to_final (size_t * amt, struct rx_dfa * frame, const t_uchar * burst, size_t len) { size_t initial_len; rx_transition_table inx_table; if (!len) { *amt = 0; return 1; } initial_len = len; inx_table = frame->state->transitions; rx_unlock_superstate (frame->rx, frame->state); frame->state = 0; while (len--) { struct rx_inx * inx; rx_transition_table next_table; if (rx_poll) (*rx_poll)(); inx = rx_transition8 (inx_table, *burst); next_table = (rx_transition_table)inx->data; while (!next_table) { struct rx_superstate * state; state = rx_transitions_to_suprestate (inx_table); switch ((long)inx->inx) { case rx_backtrack: /* RX_BACKTRACK means that we've reached the empty * superstate, indicating that match can't succeed * from this point. */ frame->state = 0; frame->final_tag = 0; *amt = (initial_len - len) - 1; return 0; case rx_cache_miss: /* Because the superstate NFA is lazily constructed, * and in fact may erode from underneath us, we sometimes * have to construct the next instruction from the hard way. * This invokes one step in the lazy-conversion. */ inx = rx_handle_cache_miss (frame->rx, state, *burst, inx->data_2); if (!inx) { frame->state = 0; frame->final_tag = 0; return -1; } next_table = (rx_transition_table)inx->data; continue; /* No other instructions are legal here. */ default: while (1) panic ("unrecognized instruction in rx_dfa_advance_to_final"); } } if (inx->data_2) { frame->state = rx_transitions_to_suprestate (next_table); rx_lock_superstate (frame->rx, frame->state); frame->final_tag = (long)inx->data_2; *amt = (initial_len - len); return 1; } inx_table = next_table; ++burst; } /* Consumed all of the characters. */ frame->state = rx_transitions_to_suprestate (inx_table); rx_lock_superstate (frame->rx, frame->state); frame->final_tag = 0; *amt = initial_len; return 1; }