/* retoor */ #include "nfa.h" #include #include 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; }