|
/* retoor <retoor@molodetz.nl> */
|
|
#include "nfa.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
nfa_t *nfa_create(void) {
|
|
nfa_t *nfa = malloc(sizeof(nfa_t));
|
|
if (!nfa) return NULL;
|
|
nfa->states = NULL;
|
|
nfa->state_count = 0;
|
|
nfa->capacity = 0;
|
|
nfa->start = NULL;
|
|
nfa->group_count = 0;
|
|
nfa->anchored_start = false;
|
|
nfa->anchored_end = false;
|
|
memset(nfa->first_chars, 0, 32);
|
|
nfa->first_chars_valid = false;
|
|
nfa->literal_prefix = NULL;
|
|
nfa->prefix_len = 0;
|
|
nfa->is_pure_literal = false;
|
|
nfa->single_first_char = 0;
|
|
nfa->literal_suffix = NULL;
|
|
nfa->suffix_len = 0;
|
|
memset(nfa->alt_dispatch, 255, 256);
|
|
nfa->has_alt_dispatch = false;
|
|
return nfa;
|
|
}
|
|
|
|
static bool ast_starts_with_anchor(ast_node_t *ast) {
|
|
if (!ast) return false;
|
|
switch (ast->type) {
|
|
case AST_ANCHOR_START:
|
|
return true;
|
|
case AST_CONCAT:
|
|
return ast_starts_with_anchor(ast->left);
|
|
case AST_GROUP:
|
|
return ast_starts_with_anchor(ast->left);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool ast_ends_with_anchor(ast_node_t *ast) {
|
|
if (!ast) return false;
|
|
switch (ast->type) {
|
|
case AST_ANCHOR_END:
|
|
return true;
|
|
case AST_CONCAT:
|
|
return ast_ends_with_anchor(ast->right);
|
|
case AST_GROUP:
|
|
return ast_ends_with_anchor(ast->left);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void extract_first_chars(ast_node_t *ast, unsigned char *bitmap, bool *valid) {
|
|
if (!ast) {
|
|
*valid = false;
|
|
return;
|
|
}
|
|
switch (ast->type) {
|
|
case AST_CHAR:
|
|
bitmap[(unsigned char)ast->value >> 3] |= (1u << (ast->value & 7));
|
|
break;
|
|
case AST_DOT:
|
|
*valid = false;
|
|
break;
|
|
case AST_CONCAT:
|
|
extract_first_chars(ast->left, bitmap, valid);
|
|
break;
|
|
case AST_ALTER:
|
|
extract_first_chars(ast->left, bitmap, valid);
|
|
extract_first_chars(ast->right, bitmap, valid);
|
|
break;
|
|
case AST_STAR:
|
|
case AST_QUESTION:
|
|
*valid = false;
|
|
break;
|
|
case AST_PLUS:
|
|
extract_first_chars(ast->left, bitmap, valid);
|
|
break;
|
|
case AST_GROUP:
|
|
extract_first_chars(ast->left, bitmap, valid);
|
|
break;
|
|
case AST_ANCHOR_START:
|
|
case AST_ANCHOR_END:
|
|
break;
|
|
case AST_BRACKET:
|
|
if (ast->bracket && !ast->bracket->negated) {
|
|
for (size_t i = 0; i < ast->bracket->count; i++) {
|
|
unsigned char s = (unsigned char)ast->bracket->ranges[i].start;
|
|
unsigned char e = (unsigned char)ast->bracket->ranges[i].end;
|
|
for (unsigned int c = s; c <= e; c++) {
|
|
bitmap[c >> 3] |= (1u << (c & 7));
|
|
}
|
|
}
|
|
} else {
|
|
*valid = false;
|
|
}
|
|
break;
|
|
case AST_QUANTIFIER:
|
|
if (ast->quant.min > 0) {
|
|
extract_first_chars(ast->left, bitmap, valid);
|
|
} else {
|
|
*valid = false;
|
|
}
|
|
break;
|
|
case AST_CLASS_DIGIT:
|
|
for (char c = '0'; c <= '9'; c++) {
|
|
bitmap[(unsigned char)c >> 3] |= (1u << (c & 7));
|
|
}
|
|
break;
|
|
case AST_CLASS_WORD:
|
|
for (char c = 'a'; c <= 'z'; c++)
|
|
bitmap[(unsigned char)c >> 3] |= (1u << (c & 7));
|
|
for (char c = 'A'; c <= 'Z'; c++)
|
|
bitmap[(unsigned char)c >> 3] |= (1u << (c & 7));
|
|
for (char c = '0'; c <= '9'; c++)
|
|
bitmap[(unsigned char)c >> 3] |= (1u << (c & 7));
|
|
bitmap['_' >> 3] |= (1u << ('_' & 7));
|
|
break;
|
|
case AST_CLASS_SPACE:
|
|
bitmap[' ' >> 3] |= (1u << (' ' & 7));
|
|
bitmap['\t' >> 3] |= (1u << ('\t' & 7));
|
|
bitmap['\n' >> 3] |= (1u << ('\n' & 7));
|
|
bitmap['\r' >> 3] |= (1u << ('\r' & 7));
|
|
break;
|
|
case AST_CLASS_NDIGIT:
|
|
case AST_CLASS_NWORD:
|
|
case AST_CLASS_NSPACE:
|
|
*valid = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool ast_is_pure_literal(ast_node_t *ast) {
|
|
if (!ast) return true;
|
|
switch (ast->type) {
|
|
case AST_CHAR:
|
|
return true;
|
|
case AST_CONCAT:
|
|
return ast_is_pure_literal(ast->left) && ast_is_pure_literal(ast->right);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static size_t extract_literal_prefix(ast_node_t *ast, char *buf, size_t max_len) {
|
|
if (!ast || max_len == 0) return 0;
|
|
switch (ast->type) {
|
|
case AST_CHAR:
|
|
buf[0] = ast->value;
|
|
return 1;
|
|
case AST_CONCAT: {
|
|
size_t left_len = extract_literal_prefix(ast->left, buf, max_len);
|
|
if (left_len > 0 && ast_is_pure_literal(ast->left)) {
|
|
size_t right_len = extract_literal_prefix(ast->right, buf + left_len, max_len - left_len);
|
|
return left_len + right_len;
|
|
}
|
|
return left_len;
|
|
}
|
|
case AST_GROUP:
|
|
return extract_literal_prefix(ast->left, buf, max_len);
|
|
case AST_ANCHOR_START:
|
|
case AST_ANCHOR_END:
|
|
return 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static ast_node_t *strip_end_anchor(ast_node_t *ast) {
|
|
if (!ast) return NULL;
|
|
if (ast->type == AST_ANCHOR_END) return NULL;
|
|
if (ast->type == AST_CONCAT && ast->right && ast->right->type == AST_ANCHOR_END) {
|
|
return ast->left;
|
|
}
|
|
return ast;
|
|
}
|
|
|
|
static size_t extract_literal_suffix_rev(ast_node_t *ast, char *buf, size_t max_len) {
|
|
if (!ast || max_len == 0) return 0;
|
|
switch (ast->type) {
|
|
case AST_CHAR:
|
|
buf[0] = ast->value;
|
|
return 1;
|
|
case AST_CONCAT: {
|
|
size_t right_len = extract_literal_suffix_rev(ast->right, buf, max_len);
|
|
if (right_len == 0 || !ast_is_pure_literal(ast->right)) return right_len;
|
|
size_t left_len = extract_literal_suffix_rev(ast->left, buf + right_len, max_len - right_len);
|
|
return right_len + left_len;
|
|
}
|
|
case AST_GROUP:
|
|
return extract_literal_suffix_rev(ast->left, buf, max_len);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static size_t extract_literal_suffix(ast_node_t *ast, char *buf, size_t max_len) {
|
|
ast_node_t *stripped = strip_end_anchor(ast);
|
|
if (!stripped) return 0;
|
|
size_t len = extract_literal_suffix_rev(stripped, buf, max_len);
|
|
for (size_t i = 0; i < len / 2; i++) {
|
|
char tmp = buf[i];
|
|
buf[i] = buf[len - 1 - i];
|
|
buf[len - 1 - i] = tmp;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static void build_alt_dispatch_node(ast_node_t *ast, unsigned char *dispatch) {
|
|
if (!ast) return;
|
|
switch (ast->type) {
|
|
case AST_CHAR:
|
|
dispatch[(unsigned char)ast->value] = 1;
|
|
break;
|
|
case AST_CONCAT:
|
|
build_alt_dispatch_node(ast->left, dispatch);
|
|
break;
|
|
case AST_ALTER:
|
|
build_alt_dispatch_node(ast->left, dispatch);
|
|
build_alt_dispatch_node(ast->right, dispatch);
|
|
break;
|
|
case AST_GROUP:
|
|
build_alt_dispatch_node(ast->left, dispatch);
|
|
break;
|
|
case AST_BRACKET:
|
|
if (ast->bracket && !ast->bracket->negated) {
|
|
for (size_t i = 0; i < ast->bracket->count; i++) {
|
|
unsigned char s = (unsigned char)ast->bracket->ranges[i].start;
|
|
unsigned char e = (unsigned char)ast->bracket->ranges[i].end;
|
|
for (unsigned int c = s; c <= e; c++) {
|
|
dispatch[c] = 1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AST_CLASS_DIGIT:
|
|
for (char c = '0'; c <= '9'; c++) {
|
|
dispatch[(unsigned char)c] = 1;
|
|
}
|
|
break;
|
|
case AST_CLASS_WORD:
|
|
for (char c = 'a'; c <= 'z'; c++) dispatch[(unsigned char)c] = 1;
|
|
for (char c = 'A'; c <= 'Z'; c++) dispatch[(unsigned char)c] = 1;
|
|
for (char c = '0'; c <= '9'; c++) dispatch[(unsigned char)c] = 1;
|
|
dispatch['_'] = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool is_top_level_alternation(ast_node_t *ast) {
|
|
if (!ast) return false;
|
|
if (ast->type == AST_ALTER) return true;
|
|
if (ast->type == AST_GROUP) return is_top_level_alternation(ast->left);
|
|
return false;
|
|
}
|
|
|
|
static bool build_alt_dispatch(ast_node_t *ast, unsigned char *dispatch) {
|
|
if (!is_top_level_alternation(ast)) return false;
|
|
memset(dispatch, 255, 256);
|
|
build_alt_dispatch_node(ast, dispatch);
|
|
bool has_any = false;
|
|
for (int i = 0; i < 256; i++) {
|
|
if (dispatch[i] == 1) {
|
|
dispatch[i] = 0;
|
|
has_any = true;
|
|
}
|
|
}
|
|
return has_any;
|
|
}
|
|
|
|
static char compute_single_first_char(unsigned char *bitmap) {
|
|
int count = 0;
|
|
char first_char = 0;
|
|
for (int i = 0; i < 256; i++) {
|
|
if (bitmap[i >> 3] & (1u << (i & 7))) {
|
|
count++;
|
|
if (count == 1) first_char = (char)i;
|
|
if (count > 1) return 0;
|
|
}
|
|
}
|
|
return first_char;
|
|
}
|
|
|
|
void nfa_free(nfa_t *nfa) {
|
|
if (!nfa) return;
|
|
for (size_t i = 0; i < nfa->state_count; i++) {
|
|
free(nfa->states[i]->transitions);
|
|
free(nfa->states[i]);
|
|
}
|
|
free(nfa->states);
|
|
free(nfa->literal_prefix);
|
|
free(nfa->literal_suffix);
|
|
free(nfa);
|
|
}
|
|
|
|
static bool nfa_grow(nfa_t *nfa) {
|
|
size_t new_cap = nfa->capacity == 0 ? 16 : nfa->capacity * 2;
|
|
if (new_cap > LOREX_MAX_STATES) {
|
|
if (nfa->capacity >= LOREX_MAX_STATES) return false;
|
|
new_cap = LOREX_MAX_STATES;
|
|
}
|
|
nfa_state_t **new_states = realloc(nfa->states, new_cap * sizeof(nfa_state_t *));
|
|
if (!new_states) return false;
|
|
nfa->states = new_states;
|
|
nfa->capacity = new_cap;
|
|
return true;
|
|
}
|
|
|
|
nfa_state_t *nfa_add_state(nfa_t *nfa) {
|
|
if (nfa->state_count >= nfa->capacity) {
|
|
if (!nfa_grow(nfa)) return NULL;
|
|
}
|
|
|
|
nfa_state_t *state = malloc(sizeof(nfa_state_t));
|
|
if (!state) return NULL;
|
|
|
|
state->id = (int)nfa->state_count;
|
|
state->accepting = false;
|
|
state->transitions = NULL;
|
|
state->trans_count = 0;
|
|
state->trans_capacity = 0;
|
|
|
|
nfa->states[nfa->state_count++] = state;
|
|
return state;
|
|
}
|
|
|
|
static bool transition_grow(nfa_state_t *state) {
|
|
size_t new_cap = state->trans_capacity == 0 ? 4 : state->trans_capacity * 2;
|
|
transition_t *new_trans = realloc(state->transitions, new_cap * sizeof(transition_t));
|
|
if (!new_trans) return false;
|
|
state->transitions = new_trans;
|
|
state->trans_capacity = new_cap;
|
|
return true;
|
|
}
|
|
|
|
void nfa_add_transition(nfa_state_t *from, nfa_state_t *to, transition_type_t type, char value) {
|
|
if (from->trans_count >= from->trans_capacity) {
|
|
if (!transition_grow(from)) return;
|
|
}
|
|
transition_t *t = &from->transitions[from->trans_count++];
|
|
t->type = type;
|
|
t->value = value;
|
|
t->target = to;
|
|
t->bracket = NULL;
|
|
t->group_id = -1;
|
|
}
|
|
|
|
void nfa_add_bracket_transition(nfa_state_t *from, nfa_state_t *to, bracket_class_t *bracket) {
|
|
if (from->trans_count >= from->trans_capacity) {
|
|
if (!transition_grow(from)) return;
|
|
}
|
|
transition_t *t = &from->transitions[from->trans_count++];
|
|
t->type = TRANS_BRACKET;
|
|
t->value = '\0';
|
|
t->target = to;
|
|
t->bracket = bracket;
|
|
t->group_id = -1;
|
|
}
|
|
|
|
void nfa_add_group_transition(nfa_state_t *from, nfa_state_t *to, transition_type_t type, int group_id) {
|
|
if (from->trans_count >= from->trans_capacity) {
|
|
if (!transition_grow(from)) return;
|
|
}
|
|
transition_t *t = &from->transitions[from->trans_count++];
|
|
t->type = type;
|
|
t->value = '\0';
|
|
t->target = to;
|
|
t->bracket = NULL;
|
|
t->group_id = group_id;
|
|
}
|
|
|
|
static nfa_fragment_t build_nfa(nfa_t *nfa, ast_node_t *ast, lorex_error_t *error);
|
|
|
|
static nfa_fragment_t build_char(nfa_t *nfa, char c, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
nfa_add_transition(start, accept, TRANS_CHAR, c);
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_dot(nfa_t *nfa, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
nfa_add_transition(start, accept, TRANS_DOT, '\0');
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_class(nfa_t *nfa, transition_type_t type, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
nfa_add_transition(start, accept, type, '\0');
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_bracket(nfa_t *nfa, bracket_class_t *bracket, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
nfa_add_bracket_transition(start, accept, bracket);
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_concat(nfa_t *nfa, ast_node_t *left, ast_node_t *right, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_fragment_t left_frag = build_nfa(nfa, left, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
nfa_fragment_t right_frag = build_nfa(nfa, right, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
nfa_add_transition(left_frag.accept, right_frag.start, TRANS_EPSILON, '\0');
|
|
frag.start = left_frag.start;
|
|
frag.accept = right_frag.accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_alter(nfa_t *nfa, ast_node_t *left, ast_node_t *right, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_fragment_t left_frag = build_nfa(nfa, left, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
nfa_fragment_t right_frag = build_nfa(nfa, right, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
nfa_add_transition(start, left_frag.start, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(start, right_frag.start, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(left_frag.accept, accept, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(right_frag.accept, accept, TRANS_EPSILON, '\0');
|
|
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_star(nfa_t *nfa, ast_node_t *child, bool greedy, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
if (greedy) {
|
|
nfa_add_transition(start, child_frag.start, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(start, accept, TRANS_EPSILON, '\0');
|
|
} else {
|
|
nfa_add_transition(start, accept, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(start, child_frag.start, TRANS_EPSILON, '\0');
|
|
}
|
|
nfa_add_transition(child_frag.accept, child_frag.start, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(child_frag.accept, accept, TRANS_EPSILON, '\0');
|
|
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_plus(nfa_t *nfa, ast_node_t *child, bool greedy, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
if (greedy) {
|
|
nfa_add_transition(child_frag.accept, child_frag.start, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(child_frag.accept, accept, TRANS_EPSILON, '\0');
|
|
} else {
|
|
nfa_add_transition(child_frag.accept, accept, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(child_frag.accept, child_frag.start, TRANS_EPSILON, '\0');
|
|
}
|
|
|
|
frag.start = child_frag.start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_question(nfa_t *nfa, ast_node_t *child, bool greedy, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
if (greedy) {
|
|
nfa_add_transition(start, child_frag.start, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(start, accept, TRANS_EPSILON, '\0');
|
|
} else {
|
|
nfa_add_transition(start, accept, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(start, child_frag.start, TRANS_EPSILON, '\0');
|
|
}
|
|
nfa_add_transition(child_frag.accept, accept, TRANS_EPSILON, '\0');
|
|
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_group(nfa_t *nfa, ast_node_t *child, int group_id, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
nfa_add_group_transition(start, child_frag.start, TRANS_GROUP_START, group_id);
|
|
nfa_add_group_transition(child_frag.accept, accept, TRANS_GROUP_END, group_id);
|
|
|
|
if (group_id + 1 > nfa->group_count) {
|
|
nfa->group_count = group_id + 1;
|
|
}
|
|
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_anchor(nfa_t *nfa, transition_type_t type, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
nfa_add_transition(start, accept, type, '\0');
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, int max, bool greedy, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
|
|
if (min == 0 && max == 0) {
|
|
nfa_state_t *state = nfa_add_state(nfa);
|
|
if (!state) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
frag.start = state;
|
|
frag.accept = state;
|
|
return frag;
|
|
}
|
|
|
|
nfa_state_t *start = nfa_add_state(nfa);
|
|
if (!start) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_state_t *current = start;
|
|
|
|
for (int i = 0; i < min; i++) {
|
|
nfa_fragment_t rep = build_nfa(nfa, child, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
nfa_add_transition(current, rep.start, TRANS_EPSILON, '\0');
|
|
current = rep.accept;
|
|
}
|
|
|
|
if (max < 0) {
|
|
nfa_state_t *loop_start = nfa_add_state(nfa);
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!loop_start || !accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_add_transition(current, loop_start, TRANS_EPSILON, '\0');
|
|
|
|
nfa_fragment_t rep = build_nfa(nfa, child, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
if (greedy) {
|
|
nfa_add_transition(loop_start, rep.start, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(loop_start, accept, TRANS_EPSILON, '\0');
|
|
} else {
|
|
nfa_add_transition(loop_start, accept, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(loop_start, rep.start, TRANS_EPSILON, '\0');
|
|
}
|
|
nfa_add_transition(rep.accept, loop_start, TRANS_EPSILON, '\0');
|
|
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
} else {
|
|
nfa_state_t *accept = nfa_add_state(nfa);
|
|
if (!accept) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
|
|
nfa_add_transition(current, accept, TRANS_EPSILON, '\0');
|
|
|
|
for (int i = min; i < max; i++) {
|
|
nfa_fragment_t rep = build_nfa(nfa, child, error);
|
|
if (*error != LOREX_OK) return frag;
|
|
|
|
if (greedy) {
|
|
nfa_add_transition(current, rep.start, TRANS_EPSILON, '\0');
|
|
} else {
|
|
nfa_add_transition(current, accept, TRANS_EPSILON, '\0');
|
|
nfa_add_transition(current, rep.start, TRANS_EPSILON, '\0');
|
|
}
|
|
|
|
if (greedy) {
|
|
nfa_add_transition(rep.accept, accept, TRANS_EPSILON, '\0');
|
|
}
|
|
current = rep.accept;
|
|
}
|
|
|
|
if (!greedy) {
|
|
nfa_add_transition(current, accept, TRANS_EPSILON, '\0');
|
|
}
|
|
|
|
frag.start = start;
|
|
frag.accept = accept;
|
|
}
|
|
|
|
return frag;
|
|
}
|
|
|
|
static nfa_fragment_t build_nfa(nfa_t *nfa, ast_node_t *ast, lorex_error_t *error) {
|
|
nfa_fragment_t frag = {NULL, NULL};
|
|
|
|
if (!ast) {
|
|
nfa_state_t *state = nfa_add_state(nfa);
|
|
if (!state) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return frag;
|
|
}
|
|
frag.start = state;
|
|
frag.accept = state;
|
|
return frag;
|
|
}
|
|
|
|
switch (ast->type) {
|
|
case AST_CHAR:
|
|
return build_char(nfa, ast->value, error);
|
|
case AST_DOT:
|
|
return build_dot(nfa, error);
|
|
case AST_CONCAT:
|
|
return build_concat(nfa, ast->left, ast->right, error);
|
|
case AST_ALTER:
|
|
return build_alter(nfa, ast->left, ast->right, error);
|
|
case AST_STAR:
|
|
return build_star(nfa, ast->left, ast->quant.greedy, error);
|
|
case AST_PLUS:
|
|
return build_plus(nfa, ast->left, ast->quant.greedy, error);
|
|
case AST_QUESTION:
|
|
return build_question(nfa, ast->left, ast->quant.greedy, error);
|
|
case AST_GROUP:
|
|
return build_group(nfa, ast->left, ast->group_id, error);
|
|
case AST_ANCHOR_START:
|
|
return build_anchor(nfa, TRANS_ANCHOR_START, error);
|
|
case AST_ANCHOR_END:
|
|
return build_anchor(nfa, TRANS_ANCHOR_END, error);
|
|
case AST_BRACKET:
|
|
return build_bracket(nfa, ast->bracket, error);
|
|
case AST_QUANTIFIER:
|
|
return build_quantifier(nfa, ast->left, ast->quant.min, ast->quant.max, ast->quant.greedy, error);
|
|
case AST_CLASS_DIGIT:
|
|
return build_class(nfa, TRANS_CLASS_DIGIT, error);
|
|
case AST_CLASS_WORD:
|
|
return build_class(nfa, TRANS_CLASS_WORD, error);
|
|
case AST_CLASS_SPACE:
|
|
return build_class(nfa, TRANS_CLASS_SPACE, error);
|
|
case AST_CLASS_NDIGIT:
|
|
return build_class(nfa, TRANS_CLASS_NDIGIT, error);
|
|
case AST_CLASS_NWORD:
|
|
return build_class(nfa, TRANS_CLASS_NWORD, error);
|
|
case AST_CLASS_NSPACE:
|
|
return build_class(nfa, TRANS_CLASS_NSPACE, error);
|
|
}
|
|
|
|
return frag;
|
|
}
|
|
|
|
nfa_t *nfa_from_ast(ast_node_t *ast, lorex_error_t *error) {
|
|
*error = LOREX_OK;
|
|
nfa_t *nfa = nfa_create();
|
|
if (!nfa) {
|
|
*error = LOREX_ERR_OUT_OF_MEMORY;
|
|
return NULL;
|
|
}
|
|
|
|
nfa->anchored_start = ast_starts_with_anchor(ast);
|
|
nfa->anchored_end = ast_ends_with_anchor(ast);
|
|
|
|
nfa->first_chars_valid = true;
|
|
extract_first_chars(ast, nfa->first_chars, &nfa->first_chars_valid);
|
|
if (nfa->first_chars_valid) {
|
|
nfa->single_first_char = compute_single_first_char(nfa->first_chars);
|
|
}
|
|
|
|
nfa->is_pure_literal = ast_is_pure_literal(ast);
|
|
|
|
char prefix_buf[256];
|
|
size_t prefix_len = extract_literal_prefix(ast, prefix_buf, sizeof(prefix_buf));
|
|
if (prefix_len > 0) {
|
|
nfa->literal_prefix = malloc(prefix_len + 1);
|
|
if (nfa->literal_prefix) {
|
|
memcpy(nfa->literal_prefix, prefix_buf, prefix_len);
|
|
nfa->literal_prefix[prefix_len] = '\0';
|
|
nfa->prefix_len = prefix_len;
|
|
}
|
|
}
|
|
|
|
if (nfa->anchored_end) {
|
|
char suffix_buf[256];
|
|
size_t suffix_len = extract_literal_suffix(ast, suffix_buf, sizeof(suffix_buf));
|
|
if (suffix_len > 0) {
|
|
nfa->literal_suffix = malloc(suffix_len + 1);
|
|
if (nfa->literal_suffix) {
|
|
memcpy(nfa->literal_suffix, suffix_buf, suffix_len);
|
|
nfa->literal_suffix[suffix_len] = '\0';
|
|
nfa->suffix_len = suffix_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
nfa->has_alt_dispatch = build_alt_dispatch(ast, nfa->alt_dispatch);
|
|
|
|
nfa_fragment_t frag = build_nfa(nfa, ast, error);
|
|
if (*error != LOREX_OK) {
|
|
nfa_free(nfa);
|
|
return NULL;
|
|
}
|
|
|
|
nfa->start = frag.start;
|
|
frag.accept->accepting = true;
|
|
|
|
return nfa;
|
|
}
|