chore: rename project from loreg to lorex and add NFA optimization infrastructure
CI / build (push) Successful in 22s
CI / test (push) Successful in 14s
CI / valgrind (push) Successful in 21s
CI / coverage (push) Failing after 21s

- Rename all source files, headers, and build targets from loreg to lorex
- Add match context struct with pre-allocated state sets for reuse across calls
- Introduce optimization fields in nfa_t: literal prefix/suffix, first character bitmap, alternation dispatch table, anchor flags
- Add benchmark target with -O3 -march=native in Makefile
- Implement bracket_class_t bitmap cache for O(1) character class lookups
- Update README with performance section detailing optimization techniques
- Add coverage/ directory to .gitignore
- Update CHANGELOG to reflect reduced line counts after rename
This commit is contained in:
2026-01-04 00:58:43 +00:00
parent c97aded10f
commit 794dca4e2b
26 changed files with 1967 additions and 513 deletions
+23 -5
View File
@@ -2,6 +2,7 @@
#include "ast.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
static ast_node_t *ast_create_node(ast_type_t type) {
ast_node_t *node = malloc(sizeof(ast_node_t));
@@ -126,6 +127,8 @@ bracket_class_t *bracket_create(void) {
bracket->count = 0;
bracket->capacity = 0;
bracket->negated = false;
memset(bracket->bitmap, 0, 32);
bracket->bitmap_valid = true;
return bracket;
}
@@ -149,6 +152,14 @@ void bracket_add_range(bracket_class_t *bracket, char start, char end) {
bracket->ranges[bracket->count].start = start;
bracket->ranges[bracket->count].end = end;
bracket->count++;
if (bracket->bitmap_valid) {
unsigned char s = (unsigned char)start;
unsigned char e = (unsigned char)end;
for (unsigned int c = s; c <= e; c++) {
bracket->bitmap[c >> 3] |= (1u << (c & 7));
}
}
}
void bracket_free(bracket_class_t *bracket) {
@@ -158,11 +169,18 @@ void bracket_free(bracket_class_t *bracket) {
}
bool bracket_matches(bracket_class_t *bracket, char c) {
bool found = false;
for (size_t i = 0; i < bracket->count; i++) {
if (c >= bracket->ranges[i].start && c <= bracket->ranges[i].end) {
found = true;
break;
unsigned char uc = (unsigned char)c;
bool found;
if (bracket->bitmap_valid) {
found = (bracket->bitmap[uc >> 3] & (1u << (uc & 7))) != 0;
} else {
found = false;
for (size_t i = 0; i < bracket->count; i++) {
if (c >= bracket->ranges[i].start && c <= bracket->ranges[i].end) {
found = true;
break;
}
}
}
return bracket->negated ? !found : found;
-71
View File
@@ -1,71 +0,0 @@
/* retoor <retoor@molodetz.nl> */
#include "loreg.h"
#include "parser.h"
#include "nfa.h"
#include "matcher.h"
#include <stdlib.h>
struct loreg_regex {
nfa_t *nfa;
ast_node_t *ast;
};
loreg_regex_t *loreg_compile(const char *pattern, loreg_error_t *error) {
*error = LOREG_OK;
loreg_regex_t *regex = malloc(sizeof(loreg_regex_t));
if (!regex) {
*error = LOREG_ERR_OUT_OF_MEMORY;
return NULL;
}
parser_t parser;
parser_init(&parser, pattern);
regex->ast = parser_parse(&parser);
*error = parser_get_error(&parser);
if (*error != LOREG_OK) {
ast_free(regex->ast);
free(regex);
return NULL;
}
regex->nfa = nfa_from_ast(regex->ast, error);
if (*error != LOREG_OK) {
ast_free(regex->ast);
free(regex);
return NULL;
}
return regex;
}
void loreg_free(loreg_regex_t *regex) {
if (!regex) return;
nfa_free(regex->nfa);
ast_free(regex->ast);
free(regex);
}
bool loreg_match(loreg_regex_t *regex, const char *text, loreg_match_t *result) {
return nfa_match(regex->nfa, text, 0, result);
}
bool loreg_search(loreg_regex_t *regex, const char *text, loreg_match_t *result) {
return nfa_search(regex->nfa, text, result);
}
const char *loreg_error_string(loreg_error_t error) {
switch (error) {
case LOREG_OK: return "success";
case LOREG_ERR_INVALID_PATTERN: return "invalid pattern";
case LOREG_ERR_UNBALANCED_PAREN: return "unbalanced parentheses";
case LOREG_ERR_EMPTY_GROUP: return "empty group";
case LOREG_ERR_INVALID_QUANTIFIER: return "invalid quantifier";
case LOREG_ERR_INVALID_ESCAPE: return "invalid escape sequence";
case LOREG_ERR_OUT_OF_MEMORY: return "out of memory";
case LOREG_ERR_STATE_OVERFLOW: return "state overflow";
default: return "unknown error";
}
}
+71
View File
@@ -0,0 +1,71 @@
/* retoor <retoor@molodetz.nl> */
#include "lorex.h"
#include "parser.h"
#include "nfa.h"
#include "matcher.h"
#include <stdlib.h>
struct lorex_regex {
nfa_t *nfa;
ast_node_t *ast;
};
lorex_regex_t *lorex_compile(const char *pattern, lorex_error_t *error) {
*error = LOREX_OK;
lorex_regex_t *regex = malloc(sizeof(lorex_regex_t));
if (!regex) {
*error = LOREX_ERR_OUT_OF_MEMORY;
return NULL;
}
parser_t parser;
parser_init(&parser, pattern);
regex->ast = parser_parse(&parser);
*error = parser_get_error(&parser);
if (*error != LOREX_OK) {
ast_free(regex->ast);
free(regex);
return NULL;
}
regex->nfa = nfa_from_ast(regex->ast, error);
if (*error != LOREX_OK) {
ast_free(regex->ast);
free(regex);
return NULL;
}
return regex;
}
void lorex_free(lorex_regex_t *regex) {
if (!regex) return;
nfa_free(regex->nfa);
ast_free(regex->ast);
free(regex);
}
bool lorex_match(lorex_regex_t *regex, const char *text, lorex_match_t *result) {
return nfa_match(regex->nfa, text, 0, result);
}
bool lorex_search(lorex_regex_t *regex, const char *text, lorex_match_t *result) {
return nfa_search(regex->nfa, text, result);
}
const char *lorex_error_string(lorex_error_t error) {
switch (error) {
case LOREX_OK: return "success";
case LOREX_ERR_INVALID_PATTERN: return "invalid pattern";
case LOREX_ERR_UNBALANCED_PAREN: return "unbalanced parentheses";
case LOREX_ERR_EMPTY_GROUP: return "empty group";
case LOREX_ERR_INVALID_QUANTIFIER: return "invalid quantifier";
case LOREX_ERR_INVALID_ESCAPE: return "invalid escape sequence";
case LOREX_ERR_OUT_OF_MEMORY: return "out of memory";
case LOREX_ERR_STATE_OVERFLOW: return "state overflow";
default: return "unknown error";
}
}
+10 -10
View File
@@ -1,5 +1,5 @@
/* retoor <retoor@molodetz.nl> */
#include "loreg.h"
#include "lorex.h"
#include "repl.h"
#include <stdio.h>
#include <string.h>
@@ -20,10 +20,10 @@ static void print_usage(const char *program) {
}
static void print_version(void) {
printf("loreg %s\n", LOREG_VERSION);
printf("lorex %s\n", LOREX_VERSION);
}
static void print_match(const char *text, loreg_match_t *result) {
static void print_match(const char *text, lorex_match_t *result) {
if (!result->matched) {
printf("no match\n");
return;
@@ -86,22 +86,22 @@ int main(int argc, char *argv[]) {
const char *pattern = argv[arg_idx];
const char *text = argv[arg_idx + 1];
loreg_error_t error;
loreg_regex_t *regex = loreg_compile(pattern, &error);
lorex_error_t error;
lorex_regex_t *regex = lorex_compile(pattern, &error);
if (!regex) {
fprintf(stderr, "error: %s\n", loreg_error_string(error));
fprintf(stderr, "error: %s\n", lorex_error_string(error));
return 1;
}
loreg_match_t result;
lorex_match_t result;
if (match_mode) {
loreg_match(regex, text, &result);
lorex_match(regex, text, &result);
} else {
loreg_search(regex, text, &result);
lorex_search(regex, text, &result);
}
print_match(text, &result);
loreg_free(regex);
lorex_free(regex);
return result.matched ? 0 : 1;
}
+424 -68
View File
@@ -3,6 +3,33 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h>
#define BITMAP_SET(bm, id) ((bm)[(id) >> 3] |= (1u << ((id) & 7)))
#define BITMAP_GET(bm, id) ((bm)[(id) >> 3] & (1u << ((id) & 7)))
#define BITMAP_CLR(bm, id) ((bm)[(id) >> 3] &= ~(1u << ((id) & 7)))
#define BITMAP_SIZE(n) (((n) + 7) >> 3)
static const uint8_t char_class_digit[32] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t char_class_word[32] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x03,
0xFE, 0xFF, 0xFF, 0x87, 0xFE, 0xFF, 0xFF, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t char_class_space[32] = {
0x00, 0x26, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
state_set_t *state_set_create(size_t initial_capacity, int group_count) {
state_set_t *set = malloc(sizeof(state_set_t));
@@ -78,27 +105,26 @@ bool state_set_contains(state_set_t *set, nfa_state_t *state) {
return false;
}
static bool is_digit(char c) {
return c >= '0' && c <= '9';
static inline bool is_digit(unsigned char c) {
return (char_class_digit[c >> 3] & (1u << (c & 7))) != 0;
}
static bool is_word(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_';
static inline bool is_word(unsigned char c) {
return (char_class_word[c >> 3] & (1u << (c & 7))) != 0;
}
static bool is_space(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
static inline bool is_space(unsigned char c) {
return (char_class_space[c >> 3] & (1u << (c & 7))) != 0;
}
static bool transition_matches(transition_t *t, char c, size_t pos, size_t len) {
static inline bool transition_matches(const transition_t * restrict t, unsigned char c, size_t pos, size_t len) {
switch (t->type) {
case TRANS_CHAR:
return t->value == c;
return (unsigned char)t->value == c;
case TRANS_DOT:
return c != '\n' && c != '\0';
case TRANS_BRACKET:
return bracket_matches(t->bracket, c);
return bracket_matches(t->bracket, (char)c);
case TRANS_CLASS_DIGIT:
return is_digit(c);
case TRANS_CLASS_WORD:
@@ -131,9 +157,25 @@ typedef struct {
size_t count;
size_t capacity;
int group_count;
uint8_t *state_bitmap;
size_t bitmap_size;
size_t *scratch_starts;
size_t *scratch_ends;
} thread_list_t;
static thread_list_t *thread_list_create(size_t capacity, int group_count) {
struct match_ctx {
thread_list_t *current;
thread_list_t *next;
uint32_t *visited;
size_t *init_starts;
size_t *init_ends;
size_t *best_starts;
size_t *best_ends;
int group_count;
size_t num_states;
};
static thread_list_t *thread_list_create(size_t capacity, int group_count, size_t num_states) {
thread_list_t *list = malloc(sizeof(thread_list_t));
if (!list) return NULL;
@@ -143,6 +185,30 @@ static thread_list_t *thread_list_create(size_t capacity, int group_count) {
return NULL;
}
list->bitmap_size = BITMAP_SIZE(num_states);
list->state_bitmap = calloc(list->bitmap_size, 1);
if (!list->state_bitmap) {
free(list->threads);
free(list);
return NULL;
}
if (group_count > 0) {
list->scratch_starts = malloc(group_count * sizeof(size_t));
list->scratch_ends = malloc(group_count * sizeof(size_t));
if (!list->scratch_starts || !list->scratch_ends) {
free(list->scratch_starts);
free(list->scratch_ends);
free(list->state_bitmap);
free(list->threads);
free(list);
return NULL;
}
} else {
list->scratch_starts = NULL;
list->scratch_ends = NULL;
}
for (size_t i = 0; i < capacity; i++) {
if (group_count > 0) {
list->threads[i].group_starts = malloc(group_count * sizeof(size_t));
@@ -152,6 +218,9 @@ static thread_list_t *thread_list_create(size_t capacity, int group_count) {
free(list->threads[j].group_starts);
free(list->threads[j].group_ends);
}
free(list->scratch_starts);
free(list->scratch_ends);
free(list->state_bitmap);
free(list->threads);
free(list);
return NULL;
@@ -174,19 +243,76 @@ static void thread_list_free(thread_list_t *list) {
free(list->threads[i].group_starts);
free(list->threads[i].group_ends);
}
free(list->scratch_starts);
free(list->scratch_ends);
free(list->state_bitmap);
free(list->threads);
free(list);
}
static void thread_list_clear(thread_list_t *list);
match_ctx_t *match_ctx_create(nfa_t *nfa) {
match_ctx_t *ctx = malloc(sizeof(match_ctx_t));
if (!ctx) return NULL;
size_t num_states = nfa->state_count;
int group_count = nfa->group_count > 0 ? nfa->group_count : 1;
ctx->num_states = num_states;
ctx->group_count = group_count;
ctx->current = thread_list_create(num_states, group_count, num_states);
ctx->next = thread_list_create(num_states, group_count, num_states);
ctx->visited = calloc(num_states, sizeof(uint32_t));
ctx->init_starts = calloc(group_count, sizeof(size_t));
ctx->init_ends = calloc(group_count, sizeof(size_t));
ctx->best_starts = calloc(group_count, sizeof(size_t));
ctx->best_ends = calloc(group_count, sizeof(size_t));
if (!ctx->current || !ctx->next || !ctx->visited ||
!ctx->init_starts || !ctx->init_ends ||
!ctx->best_starts || !ctx->best_ends) {
match_ctx_free(ctx);
return NULL;
}
return ctx;
}
void match_ctx_free(match_ctx_t *ctx) {
if (!ctx) return;
thread_list_free(ctx->current);
thread_list_free(ctx->next);
free(ctx->visited);
free(ctx->init_starts);
free(ctx->init_ends);
free(ctx->best_starts);
free(ctx->best_ends);
free(ctx);
}
static void match_ctx_reset(match_ctx_t *ctx) {
thread_list_clear(ctx->current);
thread_list_clear(ctx->next);
memset(ctx->visited, 0, ctx->num_states * sizeof(uint32_t));
for (int i = 0; i < ctx->group_count; i++) {
ctx->init_starts[i] = (size_t)-1;
ctx->init_ends[i] = (size_t)-1;
ctx->best_starts[i] = (size_t)-1;
ctx->best_ends[i] = (size_t)-1;
}
}
static void thread_list_clear(thread_list_t *list) {
for (size_t i = 0; i < list->count; i++) {
BITMAP_CLR(list->state_bitmap, list->threads[i].state->id);
}
list->count = 0;
}
static bool thread_list_contains_state(thread_list_t *list, nfa_state_t *state) {
for (size_t i = 0; i < list->count; i++) {
if (list->threads[i].state == state) return true;
}
return false;
static inline bool thread_list_contains_state(const thread_list_t * restrict list, const nfa_state_t * restrict state) {
return BITMAP_GET(list->state_bitmap, state->id) != 0;
}
static void add_thread(thread_list_t *list, nfa_state_t *state,
@@ -194,44 +320,36 @@ static void add_thread(thread_list_t *list, nfa_state_t *state,
static void follow_epsilons(thread_list_t *list, nfa_state_t *state,
size_t *group_starts, size_t *group_ends,
size_t pos, size_t len, bool *visited) {
if (!state || visited[state->id]) return;
visited[state->id] = true;
size_t pos, size_t len, uint32_t *visited, uint32_t gen) {
if (!state || visited[state->id] == gen) return;
visited[state->id] = gen;
for (size_t i = 0; i < state->trans_count; i++) {
transition_t *t = &state->transitions[i];
if (t->type == TRANS_EPSILON) {
follow_epsilons(list, t->target, group_starts, group_ends,
pos, len, visited);
pos, len, visited, gen);
} else if (t->type == TRANS_GROUP_START) {
size_t *new_starts = malloc(list->group_count * sizeof(size_t));
size_t *new_ends = malloc(list->group_count * sizeof(size_t));
if (new_starts && new_ends) {
memcpy(new_starts, group_starts, list->group_count * sizeof(size_t));
memcpy(new_ends, group_ends, list->group_count * sizeof(size_t));
new_starts[t->group_id] = pos;
follow_epsilons(list, t->target, new_starts, new_ends,
pos, len, visited);
}
free(new_starts);
free(new_ends);
size_t *scratch_s = list->scratch_starts;
size_t *scratch_e = list->scratch_ends;
memcpy(scratch_s, group_starts, list->group_count * sizeof(size_t));
memcpy(scratch_e, group_ends, list->group_count * sizeof(size_t));
scratch_s[t->group_id] = pos;
follow_epsilons(list, t->target, scratch_s, scratch_e,
pos, len, visited, gen);
} else if (t->type == TRANS_GROUP_END) {
size_t *new_starts = malloc(list->group_count * sizeof(size_t));
size_t *new_ends = malloc(list->group_count * sizeof(size_t));
if (new_starts && new_ends) {
memcpy(new_starts, group_starts, list->group_count * sizeof(size_t));
memcpy(new_ends, group_ends, list->group_count * sizeof(size_t));
new_ends[t->group_id] = pos;
follow_epsilons(list, t->target, new_starts, new_ends,
pos, len, visited);
}
free(new_starts);
free(new_ends);
size_t *scratch_s = list->scratch_starts;
size_t *scratch_e = list->scratch_ends;
memcpy(scratch_s, group_starts, list->group_count * sizeof(size_t));
memcpy(scratch_e, group_ends, list->group_count * sizeof(size_t));
scratch_e[t->group_id] = pos;
follow_epsilons(list, t->target, scratch_s, scratch_e,
pos, len, visited, gen);
} else if (t->type == TRANS_ANCHOR_START || t->type == TRANS_ANCHOR_END) {
if (transition_matches(t, '\0', pos, len)) {
follow_epsilons(list, t->target, group_starts, group_ends,
pos, len, visited);
pos, len, visited, gen);
}
}
}
@@ -243,9 +361,9 @@ static void add_thread(thread_list_t *list, nfa_state_t *state,
size_t *group_starts, size_t *group_ends) {
if (!state) return;
if (thread_list_contains_state(list, state)) return;
if (list->count >= list->capacity) return;
BITMAP_SET(list->state_bitmap, state->id);
thread_t *thread = &list->threads[list->count++];
thread->state = state;
if (list->group_count > 0) {
@@ -254,14 +372,15 @@ static void add_thread(thread_list_t *list, nfa_state_t *state,
}
}
bool nfa_match(nfa_t *nfa, const char *text, size_t start_pos, loreg_match_t *result) {
bool nfa_match(nfa_t *nfa, const char *text, size_t start_pos, lorex_match_t *result) {
size_t len = strlen(text);
size_t num_states = nfa->state_count;
int group_count = nfa->group_count > 0 ? nfa->group_count : 1;
thread_list_t *current = thread_list_create(num_states, group_count);
thread_list_t *next = thread_list_create(num_states, group_count);
bool *visited = calloc(num_states, sizeof(bool));
thread_list_t *current = thread_list_create(num_states, group_count, num_states);
thread_list_t *next = thread_list_create(num_states, group_count, num_states);
uint32_t *visited = calloc(num_states, sizeof(uint32_t));
uint32_t generation = 1;
if (!current || !next || !visited) {
thread_list_free(current);
@@ -286,9 +405,8 @@ bool nfa_match(nfa_t *nfa, const char *text, size_t start_pos, loreg_match_t *re
init_ends[i] = (size_t)-1;
}
memset(visited, 0, num_states * sizeof(bool));
follow_epsilons(current, nfa->start, init_starts, init_ends,
start_pos, len, visited);
start_pos, len, visited, generation++);
bool matched = false;
size_t match_end = start_pos;
@@ -322,27 +440,29 @@ bool nfa_match(nfa_t *nfa, const char *text, size_t start_pos, loreg_match_t *re
}
for (size_t pos = start_pos; pos < len; pos++) {
char c = text[pos];
unsigned char c = (unsigned char)text[pos];
thread_list_clear(next);
for (size_t i = 0; i < current->count; i++) {
thread_t *thread = &current->threads[i];
nfa_state_t *state = thread->state;
size_t trans_count = state->trans_count;
transition_t *transitions = state->transitions;
for (size_t j = 0; j < state->trans_count; j++) {
transition_t *t = &state->transitions[j];
for (size_t j = 0; j < trans_count; j++) {
transition_t *t = &transitions[j];
transition_type_t type = t->type;
if (t->type != TRANS_EPSILON &&
t->type != TRANS_GROUP_START &&
t->type != TRANS_GROUP_END &&
t->type != TRANS_ANCHOR_START &&
t->type != TRANS_ANCHOR_END) {
if (type != TRANS_EPSILON &&
type != TRANS_GROUP_START &&
type != TRANS_GROUP_END &&
type != TRANS_ANCHOR_START &&
type != TRANS_ANCHOR_END) {
if (transition_matches(t, c, pos, len)) {
memset(visited, 0, num_states * sizeof(bool));
follow_epsilons(next, t->target,
thread->group_starts, thread->group_ends,
pos + 1, len, visited);
pos + 1, len, visited, generation++);
}
}
}
@@ -371,7 +491,7 @@ bool nfa_match(nfa_t *nfa, const char *text, size_t start_pos, loreg_match_t *re
result->match_end = matched ? match_end : start_pos;
result->group_count = nfa->group_count;
for (int i = 0; i < LOREG_MAX_GROUPS && i < nfa->group_count; i++) {
for (int i = 0; i < LOREX_MAX_GROUPS && i < nfa->group_count; i++) {
result->groups[i].start = best_starts[i];
result->groups[i].end = best_ends[i];
result->groups[i].matched = (best_starts[i] != (size_t)-1 && best_ends[i] != (size_t)-1);
@@ -389,23 +509,259 @@ bool nfa_match(nfa_t *nfa, const char *text, size_t start_pos, loreg_match_t *re
return matched;
}
bool nfa_search(nfa_t *nfa, const char *text, loreg_match_t *result) {
bool nfa_match_with_ctx(nfa_t *nfa, const char *text, size_t start_pos, lorex_match_t *result, match_ctx_t *ctx) {
size_t len = strlen(text);
int group_count = ctx->group_count;
uint32_t generation = 1;
for (size_t i = 0; i <= len; i++) {
if (nfa_match(nfa, text, i, result)) {
if (result) {
result->match_start = i;
}
return true;
match_ctx_reset(ctx);
follow_epsilons(ctx->current, nfa->start, ctx->init_starts, ctx->init_ends,
start_pos, len, ctx->visited, generation++);
bool matched = false;
size_t match_end = start_pos;
for (size_t i = 0; i < ctx->current->count; i++) {
if (ctx->current->threads[i].state->accepting) {
matched = true;
match_end = start_pos;
memcpy(ctx->best_starts, ctx->current->threads[i].group_starts, group_count * sizeof(size_t));
memcpy(ctx->best_ends, ctx->current->threads[i].group_ends, group_count * sizeof(size_t));
break;
}
}
thread_list_t *current = ctx->current;
thread_list_t *next = ctx->next;
for (size_t pos = start_pos; pos < len; pos++) {
unsigned char c = (unsigned char)text[pos];
thread_list_clear(next);
for (size_t i = 0; i < current->count; i++) {
thread_t *thread = &current->threads[i];
nfa_state_t *state = thread->state;
size_t trans_count = state->trans_count;
transition_t *transitions = state->transitions;
for (size_t j = 0; j < trans_count; j++) {
transition_t *t = &transitions[j];
transition_type_t type = t->type;
if (type != TRANS_EPSILON &&
type != TRANS_GROUP_START &&
type != TRANS_GROUP_END &&
type != TRANS_ANCHOR_START &&
type != TRANS_ANCHOR_END) {
if (transition_matches(t, c, pos, len)) {
follow_epsilons(next, t->target,
thread->group_starts, thread->group_ends,
pos + 1, len, ctx->visited, generation++);
}
}
}
}
if (next->count == 0) break;
thread_list_t *tmp = current;
current = next;
next = tmp;
for (size_t i = 0; i < current->count; i++) {
if (current->threads[i].state->accepting) {
matched = true;
match_end = pos + 1;
memcpy(ctx->best_starts, current->threads[i].group_starts, group_count * sizeof(size_t));
memcpy(ctx->best_ends, current->threads[i].group_ends, group_count * sizeof(size_t));
break;
}
}
}
if (result) {
result->matched = matched;
result->match_start = start_pos;
result->match_end = matched ? match_end : start_pos;
result->group_count = nfa->group_count;
for (int i = 0; i < LOREX_MAX_GROUPS && i < nfa->group_count; i++) {
result->groups[i].start = ctx->best_starts[i];
result->groups[i].end = ctx->best_ends[i];
result->groups[i].matched = (ctx->best_starts[i] != (size_t)-1 && ctx->best_ends[i] != (size_t)-1);
}
}
return matched;
}
static void set_no_match(lorex_match_t *result) {
if (result) {
result->matched = false;
result->match_start = 0;
result->match_end = 0;
result->group_count = 0;
}
}
bool nfa_search(nfa_t *nfa, const char *text, lorex_match_t *result) {
size_t len = strlen(text);
if (nfa->anchored_start) {
bool matched = nfa_match(nfa, text, 0, result);
if (matched && result) {
result->match_start = 0;
}
if (!matched) {
set_no_match(result);
}
return matched;
}
if (nfa->is_pure_literal && nfa->literal_prefix && nfa->prefix_len > 0) {
const char *found = strstr(text, nfa->literal_prefix);
if (found) {
if (result) {
result->matched = true;
result->match_start = (size_t)(found - text);
result->match_end = result->match_start + nfa->prefix_len;
result->group_count = 0;
}
return true;
}
set_no_match(result);
return false;
}
match_ctx_t *ctx = match_ctx_create(nfa);
if (!ctx) {
set_no_match(result);
return false;
}
if (nfa->anchored_end && nfa->suffix_len > 0) {
if (len < nfa->suffix_len) {
match_ctx_free(ctx);
set_no_match(result);
return false;
}
if (memcmp(text + len - nfa->suffix_len, nfa->literal_suffix, nfa->suffix_len) != 0) {
match_ctx_free(ctx);
set_no_match(result);
return false;
}
size_t suffix_start = len - nfa->suffix_len;
for (size_t i = suffix_start + 1; i > 0; i--) {
size_t pos = i - 1;
if (nfa_match_with_ctx(nfa, text, pos, result, ctx)) {
if (result) result->match_start = pos;
match_ctx_free(ctx);
return true;
}
}
match_ctx_free(ctx);
set_no_match(result);
return false;
}
if (nfa->has_alt_dispatch) {
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)text[i];
if (nfa->alt_dispatch[c] == 255) continue;
if (nfa_match_with_ctx(nfa, text, i, result, ctx)) {
if (result) result->match_start = i;
match_ctx_free(ctx);
return true;
}
}
match_ctx_free(ctx);
set_no_match(result);
return false;
}
if (nfa->prefix_len >= 2) {
const char *pos = text;
const char *end = text + len;
while (pos <= end - nfa->prefix_len) {
pos = strstr(pos, nfa->literal_prefix);
if (!pos) break;
size_t offset = (size_t)(pos - text);
if (nfa_match_with_ctx(nfa, text, offset, result, ctx)) {
if (result) {
result->match_start = offset;
}
match_ctx_free(ctx);
return true;
}
pos++;
}
match_ctx_free(ctx);
set_no_match(result);
return false;
}
if (nfa->single_first_char != 0) {
const char *pos = text;
const char *end = text + len;
while (pos < end) {
pos = memchr(pos, nfa->single_first_char, (size_t)(end - pos));
if (!pos) break;
size_t offset = (size_t)(pos - text);
if (nfa_match_with_ctx(nfa, text, offset, result, ctx)) {
if (result) {
result->match_start = offset;
}
match_ctx_free(ctx);
return true;
}
pos++;
}
match_ctx_free(ctx);
set_no_match(result);
return false;
}
if (nfa->first_chars_valid) {
bool has_any_first_char = false;
for (int i = 0; i < 32; i++) {
if (nfa->first_chars[i]) {
has_any_first_char = true;
break;
}
}
if (has_any_first_char) {
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)text[i];
if (!(nfa->first_chars[c >> 3] & (1u << (c & 7)))) {
continue;
}
if (nfa_match_with_ctx(nfa, text, i, result, ctx)) {
if (result) {
result->match_start = i;
}
match_ctx_free(ctx);
return true;
}
}
match_ctx_free(ctx);
set_no_match(result);
return false;
}
}
for (size_t i = 0; i <= len; i++) {
if (nfa_match_with_ctx(nfa, text, i, result, ctx)) {
if (result) {
result->match_start = i;
}
match_ctx_free(ctx);
return true;
}
}
match_ctx_free(ctx);
set_no_match(result);
return false;
}
+359 -47
View File
@@ -11,9 +11,282 @@ nfa_t *nfa_create(void) {
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++) {
@@ -21,14 +294,16 @@ void nfa_free(nfa_t *nfa) {
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 > LOREG_MAX_STATES) {
if (nfa->capacity >= LOREG_MAX_STATES) return false;
new_cap = LOREG_MAX_STATES;
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;
@@ -100,14 +375,14 @@ void nfa_add_group_transition(nfa_state_t *from, nfa_state_t *to, transition_typ
t->group_id = group_id;
}
static nfa_fragment_t build_nfa(nfa_t *nfa, ast_node_t *ast, loreg_error_t *error);
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, loreg_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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_add_transition(start, accept, TRANS_CHAR, c);
@@ -116,12 +391,12 @@ static nfa_fragment_t build_char(nfa_t *nfa, char c, loreg_error_t *error) {
return frag;
}
static nfa_fragment_t build_dot(nfa_t *nfa, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_add_transition(start, accept, TRANS_DOT, '\0');
@@ -130,12 +405,12 @@ static nfa_fragment_t build_dot(nfa_t *nfa, loreg_error_t *error) {
return frag;
}
static nfa_fragment_t build_class(nfa_t *nfa, transition_type_t type, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_add_transition(start, accept, type, '\0');
@@ -144,12 +419,12 @@ static nfa_fragment_t build_class(nfa_t *nfa, transition_type_t type, loreg_erro
return frag;
}
static nfa_fragment_t build_bracket(nfa_t *nfa, bracket_class_t *bracket, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_add_bracket_transition(start, accept, bracket);
@@ -158,12 +433,12 @@ static nfa_fragment_t build_bracket(nfa_t *nfa, bracket_class_t *bracket, loreg_
return frag;
}
static nfa_fragment_t build_concat(nfa_t *nfa, ast_node_t *left, ast_node_t *right, loreg_error_t *error) {
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 != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
nfa_fragment_t right_frag = build_nfa(nfa, right, error);
if (*error != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
nfa_add_transition(left_frag.accept, right_frag.start, TRANS_EPSILON, '\0');
frag.start = left_frag.start;
@@ -171,19 +446,19 @@ static nfa_fragment_t build_concat(nfa_t *nfa, ast_node_t *left, ast_node_t *rig
return frag;
}
static nfa_fragment_t build_alter(nfa_t *nfa, ast_node_t *left, ast_node_t *right, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_fragment_t left_frag = build_nfa(nfa, left, error);
if (*error != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
nfa_fragment_t right_frag = build_nfa(nfa, right, error);
if (*error != LOREG_OK) return frag;
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');
@@ -195,17 +470,17 @@ static nfa_fragment_t build_alter(nfa_t *nfa, ast_node_t *left, ast_node_t *righ
return frag;
}
static nfa_fragment_t build_star(nfa_t *nfa, ast_node_t *child, bool greedy, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
if (greedy) {
nfa_add_transition(start, child_frag.start, TRANS_EPSILON, '\0');
@@ -222,16 +497,16 @@ static nfa_fragment_t build_star(nfa_t *nfa, ast_node_t *child, bool greedy, lor
return frag;
}
static nfa_fragment_t build_plus(nfa_t *nfa, ast_node_t *child, bool greedy, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
if (greedy) {
nfa_add_transition(child_frag.accept, child_frag.start, TRANS_EPSILON, '\0');
@@ -246,17 +521,17 @@ static nfa_fragment_t build_plus(nfa_t *nfa, ast_node_t *child, bool greedy, lor
return frag;
}
static nfa_fragment_t build_question(nfa_t *nfa, ast_node_t *child, bool greedy, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
if (greedy) {
nfa_add_transition(start, child_frag.start, TRANS_EPSILON, '\0');
@@ -272,17 +547,17 @@ static nfa_fragment_t build_question(nfa_t *nfa, ast_node_t *child, bool greedy,
return frag;
}
static nfa_fragment_t build_group(nfa_t *nfa, ast_node_t *child, int group_id, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_OK) return frag;
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);
@@ -296,12 +571,12 @@ static nfa_fragment_t build_group(nfa_t *nfa, ast_node_t *child, int group_id, l
return frag;
}
static nfa_fragment_t build_anchor(nfa_t *nfa, transition_type_t type, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
nfa_add_transition(start, accept, type, '\0');
@@ -310,13 +585,13 @@ static nfa_fragment_t build_anchor(nfa_t *nfa, transition_type_t type, loreg_err
return frag;
}
static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, int max, bool greedy, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
frag.start = state;
@@ -326,7 +601,7 @@ static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, i
nfa_state_t *start = nfa_add_state(nfa);
if (!start) {
*error = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
@@ -334,7 +609,7 @@ static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, i
for (int i = 0; i < min; i++) {
nfa_fragment_t rep = build_nfa(nfa, child, error);
if (*error != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
nfa_add_transition(current, rep.start, TRANS_EPSILON, '\0');
current = rep.accept;
}
@@ -343,14 +618,14 @@ static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, i
nfa_state_t *loop_start = nfa_add_state(nfa);
nfa_state_t *accept = nfa_add_state(nfa);
if (!loop_start || !accept) {
*error = LOREG_ERR_OUT_OF_MEMORY;
*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 != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
if (greedy) {
nfa_add_transition(loop_start, rep.start, TRANS_EPSILON, '\0');
@@ -366,7 +641,7 @@ static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, i
} else {
nfa_state_t *accept = nfa_add_state(nfa);
if (!accept) {
*error = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
@@ -374,7 +649,7 @@ static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, i
for (int i = min; i < max; i++) {
nfa_fragment_t rep = build_nfa(nfa, child, error);
if (*error != LOREG_OK) return frag;
if (*error != LOREX_OK) return frag;
if (greedy) {
nfa_add_transition(current, rep.start, TRANS_EPSILON, '\0');
@@ -400,13 +675,13 @@ static nfa_fragment_t build_quantifier(nfa_t *nfa, ast_node_t *child, int min, i
return frag;
}
static nfa_fragment_t build_nfa(nfa_t *nfa, ast_node_t *ast, loreg_error_t *error) {
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 = LOREG_ERR_OUT_OF_MEMORY;
*error = LOREX_ERR_OUT_OF_MEMORY;
return frag;
}
frag.start = state;
@@ -456,16 +731,53 @@ static nfa_fragment_t build_nfa(nfa_t *nfa, ast_node_t *ast, loreg_error_t *erro
return frag;
}
nfa_t *nfa_from_ast(ast_node_t *ast, loreg_error_t *error) {
*error = LOREG_OK;
nfa_t *nfa_from_ast(ast_node_t *ast, lorex_error_t *error) {
*error = LOREX_OK;
nfa_t *nfa = nfa_create();
if (!nfa) {
*error = LOREG_ERR_OUT_OF_MEMORY;
*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 != LOREG_OK) {
if (*error != LOREX_OK) {
nfa_free(nfa);
return NULL;
}
+20 -14
View File
@@ -10,11 +10,11 @@ static void parser_advance(parser_t *parser) {
void parser_init(parser_t *parser, const char *pattern) {
lexer_init(&parser->lexer, pattern);
parser->current = lexer_next(&parser->lexer);
parser->error = LOREG_OK;
parser->error = LOREX_OK;
parser->group_count = 0;
}
loreg_error_t parser_get_error(parser_t *parser) {
lorex_error_t parser_get_error(parser_t *parser) {
return parser->error;
}
@@ -27,7 +27,7 @@ static int parse_number(parser_t *parser);
static ast_node_t *parse_expr(parser_t *parser) {
ast_node_t *left = parse_term(parser);
if (!left || parser->error != LOREG_OK) return left;
if (!left || parser->error != LOREX_OK) return left;
while (parser->current.type == TOKEN_PIPE) {
parser_advance(parser);
@@ -38,7 +38,7 @@ static ast_node_t *parse_expr(parser_t *parser) {
}
left = ast_create_alter(left, right);
if (!left) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
parser->error = LOREX_ERR_OUT_OF_MEMORY;
return NULL;
}
}
@@ -61,7 +61,7 @@ static ast_node_t *parse_term(parser_t *parser) {
} else {
left = ast_create_concat(left, factor);
if (!left) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
parser->error = LOREX_ERR_OUT_OF_MEMORY;
return NULL;
}
}
@@ -71,7 +71,7 @@ static ast_node_t *parse_term(parser_t *parser) {
static ast_node_t *parse_factor(parser_t *parser) {
ast_node_t *atom = parse_atom(parser);
if (!atom || parser->error != LOREG_OK) return atom;
if (!atom || parser->error != LOREX_OK) return atom;
while (parser->current.type == TOKEN_STAR ||
parser->current.type == TOKEN_PLUS ||
@@ -107,7 +107,13 @@ static ast_node_t *parse_factor(parser_t *parser) {
}
if (parser->current.type != TOKEN_RBRACE) {
parser->error = LOREG_ERR_INVALID_QUANTIFIER;
parser->error = LOREX_ERR_INVALID_QUANTIFIER;
ast_free(atom);
return NULL;
}
if (max != -1 && min > max) {
parser->error = LOREX_ERR_INVALID_QUANTIFIER;
ast_free(atom);
return NULL;
}
@@ -122,7 +128,7 @@ static ast_node_t *parse_factor(parser_t *parser) {
}
if (!atom) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
parser->error = LOREX_ERR_OUT_OF_MEMORY;
return NULL;
}
}
@@ -167,7 +173,7 @@ static ast_node_t *parse_atom(parser_t *parser) {
int group_id = parser->group_count++;
ast_node_t *inner = parse_expr(parser);
if (parser->current.type != TOKEN_RPAREN) {
parser->error = LOREG_ERR_UNBALANCED_PAREN;
parser->error = LOREX_ERR_UNBALANCED_PAREN;
ast_free(inner);
return NULL;
}
@@ -216,12 +222,12 @@ static ast_node_t *parse_atom(parser_t *parser) {
return NULL;
default:
parser->error = LOREG_ERR_INVALID_PATTERN;
parser->error = LOREX_ERR_INVALID_PATTERN;
return NULL;
}
if (!node && parser->error == LOREG_OK) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
if (!node && parser->error == LOREX_OK) {
parser->error = LOREX_ERR_OUT_OF_MEMORY;
}
return node;
}
@@ -231,7 +237,7 @@ static ast_node_t *parse_bracket(parser_t *parser) {
bracket_class_t *bracket = bracket_create();
if (!bracket) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
parser->error = LOREX_ERR_OUT_OF_MEMORY;
return NULL;
}
@@ -293,7 +299,7 @@ static ast_node_t *parse_bracket(parser_t *parser) {
if (parser->current.type != TOKEN_RBRACKET) {
bracket_free(bracket);
parser->error = LOREG_ERR_INVALID_PATTERN;
parser->error = LOREX_ERR_INVALID_PATTERN;
return NULL;
}
parser_advance(parser);
+16 -16
View File
@@ -1,6 +1,6 @@
/* retoor <retoor@molodetz.nl> */
#include "repl.h"
#include "loreg.h"
#include "lorex.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -8,12 +8,12 @@
#define MAX_INPUT 4096
static void print_banner(void) {
printf("loreg v%s - regex interpreter\n", LOREG_VERSION);
printf("lorex v%s - regex interpreter\n", LOREX_VERSION);
printf("commands: :q quit, :h help, :p <pattern> set pattern, :m <text> match, :s <text> search\n\n");
}
static void print_help(void) {
printf("loreg REPL commands:\n");
printf("lorex REPL commands:\n");
printf(" :q quit\n");
printf(" :h show this help\n");
printf(" :p <regex> compile and set pattern\n");
@@ -40,7 +40,7 @@ static void print_help(void) {
printf(" \\D \\W \\S negated classes\n\n");
}
static void print_match(const char *text, loreg_match_t *result) {
static void print_match(const char *text, lorex_match_t *result) {
if (!result->matched) {
printf("no match\n");
return;
@@ -83,7 +83,7 @@ static char *read_line(void) {
void repl_run(void) {
print_banner();
loreg_regex_t *regex = NULL;
lorex_regex_t *regex = NULL;
char *line;
while ((line = read_line()) != NULL) {
@@ -103,14 +103,14 @@ void repl_run(void) {
while (*pattern == ' ') pattern++;
if (regex) {
loreg_free(regex);
lorex_free(regex);
regex = NULL;
}
loreg_error_t error;
regex = loreg_compile(pattern, &error);
lorex_error_t error;
regex = lorex_compile(pattern, &error);
if (!regex) {
printf("error: %s\n", loreg_error_string(error));
printf("error: %s\n", lorex_error_string(error));
} else {
printf("pattern compiled: %s\n", pattern);
}
@@ -126,8 +126,8 @@ void repl_run(void) {
const char *text = line + 3;
while (*text == ' ') text++;
loreg_match_t result;
loreg_match(regex, text, &result);
lorex_match_t result;
lorex_match(regex, text, &result);
print_match(text, &result);
continue;
}
@@ -141,8 +141,8 @@ void repl_run(void) {
const char *text = line + 3;
while (*text == ' ') text++;
loreg_match_t result;
loreg_search(regex, text, &result);
lorex_match_t result;
lorex_search(regex, text, &result);
print_match(text, &result);
continue;
}
@@ -157,13 +157,13 @@ void repl_run(void) {
continue;
}
loreg_match_t result;
loreg_search(regex, line, &result);
lorex_match_t result;
lorex_search(regex, line, &result);
print_match(line, &result);
}
if (regex) {
loreg_free(regex);
lorex_free(regex);
}
printf("\n");