chore: add initial CI pipeline with Gitea Actions, Makefile, and project scaffolding for loreg regex engine

This commit is contained in:
2026-01-03 23:04:48 +00:00
commit c97aded10f
26 changed files with 4459 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
/* retoor <retoor@molodetz.nl> */
#include "ast.h"
#include <stdlib.h>
#include <ctype.h>
static ast_node_t *ast_create_node(ast_type_t type) {
ast_node_t *node = malloc(sizeof(ast_node_t));
if (!node) return NULL;
node->type = type;
node->value = '\0';
node->left = NULL;
node->right = NULL;
node->group_id = -1;
node->bracket = NULL;
node->quant.min = 0;
node->quant.max = -1;
node->quant.greedy = true;
return node;
}
ast_node_t *ast_create_char(char c) {
ast_node_t *node = ast_create_node(AST_CHAR);
if (node) node->value = c;
return node;
}
ast_node_t *ast_create_dot(void) {
return ast_create_node(AST_DOT);
}
ast_node_t *ast_create_concat(ast_node_t *left, ast_node_t *right) {
ast_node_t *node = ast_create_node(AST_CONCAT);
if (node) {
node->left = left;
node->right = right;
}
return node;
}
ast_node_t *ast_create_alter(ast_node_t *left, ast_node_t *right) {
ast_node_t *node = ast_create_node(AST_ALTER);
if (node) {
node->left = left;
node->right = right;
}
return node;
}
ast_node_t *ast_create_star(ast_node_t *child, bool greedy) {
ast_node_t *node = ast_create_node(AST_STAR);
if (node) {
node->left = child;
node->quant.greedy = greedy;
}
return node;
}
ast_node_t *ast_create_plus(ast_node_t *child, bool greedy) {
ast_node_t *node = ast_create_node(AST_PLUS);
if (node) {
node->left = child;
node->quant.greedy = greedy;
}
return node;
}
ast_node_t *ast_create_question(ast_node_t *child, bool greedy) {
ast_node_t *node = ast_create_node(AST_QUESTION);
if (node) {
node->left = child;
node->quant.greedy = greedy;
}
return node;
}
ast_node_t *ast_create_group(ast_node_t *child, int group_id) {
ast_node_t *node = ast_create_node(AST_GROUP);
if (node) {
node->left = child;
node->group_id = group_id;
}
return node;
}
ast_node_t *ast_create_anchor_start(void) {
return ast_create_node(AST_ANCHOR_START);
}
ast_node_t *ast_create_anchor_end(void) {
return ast_create_node(AST_ANCHOR_END);
}
ast_node_t *ast_create_bracket(bracket_class_t *bracket) {
ast_node_t *node = ast_create_node(AST_BRACKET);
if (node) node->bracket = bracket;
return node;
}
ast_node_t *ast_create_quantifier(ast_node_t *child, int min, int max, bool greedy) {
ast_node_t *node = ast_create_node(AST_QUANTIFIER);
if (node) {
node->left = child;
node->quant.min = min;
node->quant.max = max;
node->quant.greedy = greedy;
}
return node;
}
ast_node_t *ast_create_class(ast_type_t type) {
return ast_create_node(type);
}
void ast_free(ast_node_t *node) {
if (!node) return;
ast_free(node->left);
ast_free(node->right);
if (node->bracket) bracket_free(node->bracket);
free(node);
}
bracket_class_t *bracket_create(void) {
bracket_class_t *bracket = malloc(sizeof(bracket_class_t));
if (!bracket) return NULL;
bracket->ranges = NULL;
bracket->count = 0;
bracket->capacity = 0;
bracket->negated = false;
return bracket;
}
static bool bracket_grow(bracket_class_t *bracket) {
size_t new_cap = bracket->capacity == 0 ? 8 : bracket->capacity * 2;
char_range_t *new_ranges = realloc(bracket->ranges, new_cap * sizeof(char_range_t));
if (!new_ranges) return false;
bracket->ranges = new_ranges;
bracket->capacity = new_cap;
return true;
}
void bracket_add_char(bracket_class_t *bracket, char c) {
bracket_add_range(bracket, c, c);
}
void bracket_add_range(bracket_class_t *bracket, char start, char end) {
if (bracket->count >= bracket->capacity) {
if (!bracket_grow(bracket)) return;
}
bracket->ranges[bracket->count].start = start;
bracket->ranges[bracket->count].end = end;
bracket->count++;
}
void bracket_free(bracket_class_t *bracket) {
if (!bracket) return;
free(bracket->ranges);
free(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;
}
}
return bracket->negated ? !found : found;
}
+125
View File
@@ -0,0 +1,125 @@
/* retoor <retoor@molodetz.nl> */
#include "lexer.h"
#include <string.h>
void lexer_init(lexer_t *lexer, const char *pattern) {
lexer->pattern = pattern;
lexer->length = strlen(pattern);
lexer->position = 0;
lexer->in_bracket = false;
}
static token_t make_token(token_type_t type, char value, size_t pos) {
token_t token;
token.type = type;
token.value = value;
token.position = pos;
return token;
}
token_t lexer_next(lexer_t *lexer) {
if (lexer->position >= lexer->length) {
return make_token(TOKEN_EOF, '\0', lexer->position);
}
char c = lexer->pattern[lexer->position];
size_t pos = lexer->position;
lexer->position++;
if (c == '[' && !lexer->in_bracket) {
lexer->in_bracket = true;
return make_token(TOKEN_LBRACKET, c, pos);
}
if (c == ']' && lexer->in_bracket) {
lexer->in_bracket = false;
return make_token(TOKEN_RBRACKET, c, pos);
}
if (lexer->in_bracket) {
if (c == '-') {
return make_token(TOKEN_DASH, c, pos);
}
if (c == '^' && pos > 0 && lexer->pattern[pos - 1] == '[') {
return make_token(TOKEN_CARET, c, pos);
}
if (c == '\\' && lexer->position < lexer->length) {
char next = lexer->pattern[lexer->position];
lexer->position++;
switch (next) {
case 'd': return make_token(TOKEN_CLASS_DIGIT, 'd', pos);
case 'w': return make_token(TOKEN_CLASS_WORD, 'w', pos);
case 's': return make_token(TOKEN_CLASS_SPACE, 's', pos);
case 'D': return make_token(TOKEN_CLASS_NDIGIT, 'D', pos);
case 'W': return make_token(TOKEN_CLASS_NWORD, 'W', pos);
case 'S': return make_token(TOKEN_CLASS_NSPACE, 'S', pos);
case 'n': return make_token(TOKEN_CHAR, '\n', pos);
case 't': return make_token(TOKEN_CHAR, '\t', pos);
case 'r': return make_token(TOKEN_CHAR, '\r', pos);
default: return make_token(TOKEN_CHAR, next, pos);
}
}
return make_token(TOKEN_CHAR, c, pos);
}
if (c == '\\' && lexer->position < lexer->length) {
char next = lexer->pattern[lexer->position];
lexer->position++;
switch (next) {
case 'd': return make_token(TOKEN_CLASS_DIGIT, 'd', pos);
case 'w': return make_token(TOKEN_CLASS_WORD, 'w', pos);
case 's': return make_token(TOKEN_CLASS_SPACE, 's', pos);
case 'D': return make_token(TOKEN_CLASS_NDIGIT, 'D', pos);
case 'W': return make_token(TOKEN_CLASS_NWORD, 'W', pos);
case 'S': return make_token(TOKEN_CLASS_NSPACE, 'S', pos);
case 'n': return make_token(TOKEN_CHAR, '\n', pos);
case 't': return make_token(TOKEN_CHAR, '\t', pos);
case 'r': return make_token(TOKEN_CHAR, '\r', pos);
case '.':
case '*':
case '+':
case '?':
case '|':
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '^':
case '$':
case '\\':
return make_token(TOKEN_CHAR, next, pos);
default:
return make_token(TOKEN_CHAR, next, pos);
}
}
switch (c) {
case '.': return make_token(TOKEN_DOT, c, pos);
case '*': return make_token(TOKEN_STAR, c, pos);
case '+': return make_token(TOKEN_PLUS, c, pos);
case '?': return make_token(TOKEN_QUESTION, c, pos);
case '|': return make_token(TOKEN_PIPE, c, pos);
case '(': return make_token(TOKEN_LPAREN, c, pos);
case ')': return make_token(TOKEN_RPAREN, c, pos);
case '^': return make_token(TOKEN_CARET, c, pos);
case '$': return make_token(TOKEN_DOLLAR, c, pos);
case '{': return make_token(TOKEN_LBRACE, c, pos);
case '}': return make_token(TOKEN_RBRACE, c, pos);
default: return make_token(TOKEN_CHAR, c, pos);
}
}
token_t lexer_peek(lexer_t *lexer) {
size_t saved_pos = lexer->position;
bool saved_bracket = lexer->in_bracket;
token_t token = lexer_next(lexer);
lexer->position = saved_pos;
lexer->in_bracket = saved_bracket;
return token;
}
bool lexer_eof(lexer_t *lexer) {
return lexer->position >= lexer->length;
}
+71
View File
@@ -0,0 +1,71 @@
/* 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";
}
}
+107
View File
@@ -0,0 +1,107 @@
/* retoor <retoor@molodetz.nl> */
#include "loreg.h"
#include "repl.h"
#include <stdio.h>
#include <string.h>
static void print_usage(const char *program) {
printf("usage: %s [options] [pattern] [text]\n", program);
printf("options:\n");
printf(" -h, --help show this help\n");
printf(" -v, --version show version\n");
printf(" -m, --match full match mode (default is search)\n");
printf(" -i start interactive REPL\n");
printf("\n");
printf("examples:\n");
printf(" %s start REPL\n", program);
printf(" %s -i start REPL\n", program);
printf(" %s \"a+b\" \"aaab\" search pattern in text\n", program);
printf(" %s -m \"a+b\" \"aaab\" match pattern against text\n", program);
}
static void print_version(void) {
printf("loreg %s\n", LOREG_VERSION);
}
static void print_match(const char *text, loreg_match_t *result) {
if (!result->matched) {
printf("no match\n");
return;
}
printf("match: \"");
for (size_t i = result->match_start; i < result->match_end; i++) {
printf("%c", text[i]);
}
printf("\" [%zu-%zu]\n", result->match_start, result->match_end);
for (size_t i = 0; i < result->group_count; i++) {
if (result->groups[i].matched) {
printf(" group %zu: \"", i);
for (size_t j = result->groups[i].start; j < result->groups[i].end; j++) {
printf("%c", text[j]);
}
printf("\" [%zu-%zu]\n", result->groups[i].start, result->groups[i].end);
}
}
}
int main(int argc, char *argv[]) {
if (argc == 1) {
repl_run();
return 0;
}
bool match_mode = false;
int arg_idx = 1;
while (arg_idx < argc && argv[arg_idx][0] == '-') {
if (strcmp(argv[arg_idx], "-h") == 0 || strcmp(argv[arg_idx], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
if (strcmp(argv[arg_idx], "-v") == 0 || strcmp(argv[arg_idx], "--version") == 0) {
print_version();
return 0;
}
if (strcmp(argv[arg_idx], "-m") == 0 || strcmp(argv[arg_idx], "--match") == 0) {
match_mode = true;
arg_idx++;
continue;
}
if (strcmp(argv[arg_idx], "-i") == 0) {
repl_run();
return 0;
}
fprintf(stderr, "unknown option: %s\n", argv[arg_idx]);
return 1;
}
if (argc - arg_idx < 2) {
fprintf(stderr, "error: pattern and text required\n");
print_usage(argv[0]);
return 1;
}
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);
if (!regex) {
fprintf(stderr, "error: %s\n", loreg_error_string(error));
return 1;
}
loreg_match_t result;
if (match_mode) {
loreg_match(regex, text, &result);
} else {
loreg_search(regex, text, &result);
}
print_match(text, &result);
loreg_free(regex);
return result.matched ? 0 : 1;
}
+411
View File
@@ -0,0 +1,411 @@
/* retoor <retoor@molodetz.nl> */
#include "matcher.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
state_set_t *state_set_create(size_t initial_capacity, int group_count) {
state_set_t *set = malloc(sizeof(state_set_t));
if (!set) return NULL;
set->states = calloc(initial_capacity, sizeof(nfa_state_t *));
if (!set->states) {
free(set);
return NULL;
}
set->count = 0;
set->capacity = initial_capacity;
set->group_count = group_count;
if (group_count > 0) {
set->group_starts = calloc(group_count, sizeof(size_t));
set->group_ends = calloc(group_count, sizeof(size_t));
if (!set->group_starts || !set->group_ends) {
free(set->group_starts);
free(set->group_ends);
free(set->states);
free(set);
return NULL;
}
for (int i = 0; i < group_count; i++) {
set->group_starts[i] = (size_t)-1;
set->group_ends[i] = (size_t)-1;
}
} else {
set->group_starts = NULL;
set->group_ends = NULL;
}
return set;
}
void state_set_free(state_set_t *set) {
if (!set) return;
free(set->states);
free(set->group_starts);
free(set->group_ends);
free(set);
}
void state_set_clear(state_set_t *set) {
memset(set->states, 0, set->capacity * sizeof(nfa_state_t *));
set->count = 0;
}
static bool state_set_grow(state_set_t *set) {
size_t new_cap = set->capacity * 2;
nfa_state_t **new_states = realloc(set->states, new_cap * sizeof(nfa_state_t *));
if (!new_states) return false;
memset(new_states + set->capacity, 0, set->capacity * sizeof(nfa_state_t *));
set->states = new_states;
set->capacity = new_cap;
return true;
}
void state_set_add(state_set_t *set, nfa_state_t *state) {
if (state_set_contains(set, state)) return;
if (set->count >= set->capacity) {
if (!state_set_grow(set)) return;
}
set->states[set->count++] = state;
}
bool state_set_contains(state_set_t *set, nfa_state_t *state) {
for (size_t i = 0; i < set->count; i++) {
if (set->states[i] == state) return true;
}
return false;
}
static bool is_digit(char c) {
return c >= '0' && c <= '9';
}
static bool is_word(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_';
}
static bool is_space(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
}
static bool transition_matches(transition_t *t, char c, size_t pos, size_t len) {
switch (t->type) {
case TRANS_CHAR:
return t->value == c;
case TRANS_DOT:
return c != '\n' && c != '\0';
case TRANS_BRACKET:
return bracket_matches(t->bracket, c);
case TRANS_CLASS_DIGIT:
return is_digit(c);
case TRANS_CLASS_WORD:
return is_word(c);
case TRANS_CLASS_SPACE:
return is_space(c);
case TRANS_CLASS_NDIGIT:
return !is_digit(c) && c != '\0';
case TRANS_CLASS_NWORD:
return !is_word(c) && c != '\0';
case TRANS_CLASS_NSPACE:
return !is_space(c) && c != '\0';
case TRANS_ANCHOR_START:
return pos == 0;
case TRANS_ANCHOR_END:
return pos == len;
default:
return false;
}
}
typedef struct {
nfa_state_t *state;
size_t *group_starts;
size_t *group_ends;
} thread_t;
typedef struct {
thread_t *threads;
size_t count;
size_t capacity;
int group_count;
} thread_list_t;
static thread_list_t *thread_list_create(size_t capacity, int group_count) {
thread_list_t *list = malloc(sizeof(thread_list_t));
if (!list) return NULL;
list->threads = malloc(capacity * sizeof(thread_t));
if (!list->threads) {
free(list);
return NULL;
}
for (size_t i = 0; i < capacity; i++) {
if (group_count > 0) {
list->threads[i].group_starts = malloc(group_count * sizeof(size_t));
list->threads[i].group_ends = malloc(group_count * sizeof(size_t));
if (!list->threads[i].group_starts || !list->threads[i].group_ends) {
for (size_t j = 0; j <= i; j++) {
free(list->threads[j].group_starts);
free(list->threads[j].group_ends);
}
free(list->threads);
free(list);
return NULL;
}
} else {
list->threads[i].group_starts = NULL;
list->threads[i].group_ends = NULL;
}
}
list->count = 0;
list->capacity = capacity;
list->group_count = group_count;
return list;
}
static void thread_list_free(thread_list_t *list) {
if (!list) return;
for (size_t i = 0; i < list->capacity; i++) {
free(list->threads[i].group_starts);
free(list->threads[i].group_ends);
}
free(list->threads);
free(list);
}
static void thread_list_clear(thread_list_t *list) {
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 void add_thread(thread_list_t *list, nfa_state_t *state,
size_t *group_starts, size_t *group_ends);
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;
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);
} 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);
} 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);
} 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);
}
}
}
add_thread(list, state, group_starts, group_ends);
}
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;
thread_t *thread = &list->threads[list->count++];
thread->state = state;
if (list->group_count > 0) {
memcpy(thread->group_starts, group_starts, list->group_count * sizeof(size_t));
memcpy(thread->group_ends, group_ends, list->group_count * sizeof(size_t));
}
}
bool nfa_match(nfa_t *nfa, const char *text, size_t start_pos, loreg_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));
if (!current || !next || !visited) {
thread_list_free(current);
thread_list_free(next);
free(visited);
return false;
}
size_t *init_starts = calloc(group_count, sizeof(size_t));
size_t *init_ends = calloc(group_count, sizeof(size_t));
if (!init_starts || !init_ends) {
free(init_starts);
free(init_ends);
thread_list_free(current);
thread_list_free(next);
free(visited);
return false;
}
for (int i = 0; i < group_count; i++) {
init_starts[i] = (size_t)-1;
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);
bool matched = false;
size_t match_end = start_pos;
size_t *best_starts = calloc(group_count, sizeof(size_t));
size_t *best_ends = calloc(group_count, sizeof(size_t));
if (!best_starts || !best_ends) {
free(init_starts);
free(init_ends);
free(best_starts);
free(best_ends);
thread_list_free(current);
thread_list_free(next);
free(visited);
return false;
}
for (int i = 0; i < group_count; i++) {
best_starts[i] = (size_t)-1;
best_ends[i] = (size_t)-1;
}
for (size_t i = 0; i < current->count; i++) {
if (current->threads[i].state->accepting) {
matched = true;
match_end = start_pos;
memcpy(best_starts, current->threads[i].group_starts, group_count * sizeof(size_t));
memcpy(best_ends, current->threads[i].group_ends, group_count * sizeof(size_t));
break;
}
}
for (size_t pos = start_pos; pos < len; pos++) {
char c = 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;
for (size_t j = 0; j < state->trans_count; j++) {
transition_t *t = &state->transitions[j];
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 (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);
}
}
}
}
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(best_starts, current->threads[i].group_starts, group_count * sizeof(size_t));
memcpy(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 < LOREG_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);
}
}
free(init_starts);
free(init_ends);
free(best_starts);
free(best_ends);
thread_list_free(current);
thread_list_free(next);
free(visited);
return matched;
}
bool nfa_search(nfa_t *nfa, const char *text, loreg_match_t *result) {
size_t len = strlen(text);
for (size_t i = 0; i <= len; i++) {
if (nfa_match(nfa, text, i, result)) {
if (result) {
result->match_start = i;
}
return true;
}
}
if (result) {
result->matched = false;
result->match_start = 0;
result->match_end = 0;
result->group_count = 0;
}
return false;
}
+477
View File
@@ -0,0 +1,477 @@
/* 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;
return nfa;
}
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);
}
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;
}
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, loreg_error_t *error);
static nfa_fragment_t build_char(nfa_t *nfa, char c, loreg_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;
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, loreg_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;
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, loreg_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;
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, loreg_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;
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, loreg_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;
nfa_fragment_t right_frag = build_nfa(nfa, right, error);
if (*error != LOREG_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, loreg_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;
return frag;
}
nfa_fragment_t left_frag = build_nfa(nfa, left, error);
if (*error != LOREG_OK) return frag;
nfa_fragment_t right_frag = build_nfa(nfa, right, error);
if (*error != LOREG_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, loreg_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;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_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, loreg_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;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_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, loreg_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;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_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, loreg_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;
return frag;
}
nfa_fragment_t child_frag = build_nfa(nfa, child, error);
if (*error != LOREG_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, loreg_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;
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, loreg_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;
return frag;
}
frag.start = state;
frag.accept = state;
return frag;
}
nfa_state_t *start = nfa_add_state(nfa);
if (!start) {
*error = LOREG_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 != LOREG_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 = LOREG_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 (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 = LOREG_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 != LOREG_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, loreg_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;
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, loreg_error_t *error) {
*error = LOREG_OK;
nfa_t *nfa = nfa_create();
if (!nfa) {
*error = LOREG_ERR_OUT_OF_MEMORY;
return NULL;
}
nfa_fragment_t frag = build_nfa(nfa, ast, error);
if (*error != LOREG_OK) {
nfa_free(nfa);
return NULL;
}
nfa->start = frag.start;
frag.accept->accepting = true;
return nfa;
}
+309
View File
@@ -0,0 +1,309 @@
/* retoor <retoor@molodetz.nl> */
#include "parser.h"
#include <stdlib.h>
#include <ctype.h>
static void parser_advance(parser_t *parser) {
parser->current = lexer_next(&parser->lexer);
}
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->group_count = 0;
}
loreg_error_t parser_get_error(parser_t *parser) {
return parser->error;
}
static ast_node_t *parse_expr(parser_t *parser);
static ast_node_t *parse_term(parser_t *parser);
static ast_node_t *parse_factor(parser_t *parser);
static ast_node_t *parse_atom(parser_t *parser);
static ast_node_t *parse_bracket(parser_t *parser);
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;
while (parser->current.type == TOKEN_PIPE) {
parser_advance(parser);
ast_node_t *right = parse_term(parser);
if (!right) {
ast_free(left);
return NULL;
}
left = ast_create_alter(left, right);
if (!left) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
return NULL;
}
}
return left;
}
static ast_node_t *parse_term(parser_t *parser) {
ast_node_t *left = NULL;
while (parser->current.type != TOKEN_EOF &&
parser->current.type != TOKEN_PIPE &&
parser->current.type != TOKEN_RPAREN) {
ast_node_t *factor = parse_factor(parser);
if (!factor) {
ast_free(left);
return NULL;
}
if (left == NULL) {
left = factor;
} else {
left = ast_create_concat(left, factor);
if (!left) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
return NULL;
}
}
}
return left;
}
static ast_node_t *parse_factor(parser_t *parser) {
ast_node_t *atom = parse_atom(parser);
if (!atom || parser->error != LOREG_OK) return atom;
while (parser->current.type == TOKEN_STAR ||
parser->current.type == TOKEN_PLUS ||
parser->current.type == TOKEN_QUESTION ||
parser->current.type == TOKEN_LBRACE) {
token_type_t quant_type = parser->current.type;
parser_advance(parser);
bool greedy = true;
if (parser->current.type == TOKEN_QUESTION) {
greedy = false;
parser_advance(parser);
}
if (quant_type == TOKEN_STAR) {
atom = ast_create_star(atom, greedy);
} else if (quant_type == TOKEN_PLUS) {
atom = ast_create_plus(atom, greedy);
} else if (quant_type == TOKEN_QUESTION) {
atom = ast_create_question(atom, greedy);
} else if (quant_type == TOKEN_LBRACE) {
int min = parse_number(parser);
int max = min;
if (parser->current.type == TOKEN_CHAR && parser->current.value == ',') {
parser_advance(parser);
if (parser->current.type == TOKEN_RBRACE) {
max = -1;
} else {
max = parse_number(parser);
}
}
if (parser->current.type != TOKEN_RBRACE) {
parser->error = LOREG_ERR_INVALID_QUANTIFIER;
ast_free(atom);
return NULL;
}
parser_advance(parser);
if (parser->current.type == TOKEN_QUESTION) {
greedy = false;
parser_advance(parser);
}
atom = ast_create_quantifier(atom, min, max, greedy);
}
if (!atom) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
return NULL;
}
}
return atom;
}
static int parse_number(parser_t *parser) {
int num = 0;
while (parser->current.type == TOKEN_CHAR && isdigit(parser->current.value)) {
num = num * 10 + (parser->current.value - '0');
parser_advance(parser);
}
return num;
}
static ast_node_t *parse_atom(parser_t *parser) {
ast_node_t *node = NULL;
switch (parser->current.type) {
case TOKEN_CHAR:
node = ast_create_char(parser->current.value);
parser_advance(parser);
break;
case TOKEN_DOT:
node = ast_create_dot();
parser_advance(parser);
break;
case TOKEN_CARET:
node = ast_create_anchor_start();
parser_advance(parser);
break;
case TOKEN_DOLLAR:
node = ast_create_anchor_end();
parser_advance(parser);
break;
case TOKEN_LPAREN: {
parser_advance(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;
ast_free(inner);
return NULL;
}
parser_advance(parser);
node = ast_create_group(inner, group_id);
break;
}
case TOKEN_LBRACKET:
node = parse_bracket(parser);
break;
case TOKEN_CLASS_DIGIT:
node = ast_create_class(AST_CLASS_DIGIT);
parser_advance(parser);
break;
case TOKEN_CLASS_WORD:
node = ast_create_class(AST_CLASS_WORD);
parser_advance(parser);
break;
case TOKEN_CLASS_SPACE:
node = ast_create_class(AST_CLASS_SPACE);
parser_advance(parser);
break;
case TOKEN_CLASS_NDIGIT:
node = ast_create_class(AST_CLASS_NDIGIT);
parser_advance(parser);
break;
case TOKEN_CLASS_NWORD:
node = ast_create_class(AST_CLASS_NWORD);
parser_advance(parser);
break;
case TOKEN_CLASS_NSPACE:
node = ast_create_class(AST_CLASS_NSPACE);
parser_advance(parser);
break;
case TOKEN_EOF:
case TOKEN_PIPE:
case TOKEN_RPAREN:
return NULL;
default:
parser->error = LOREG_ERR_INVALID_PATTERN;
return NULL;
}
if (!node && parser->error == LOREG_OK) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
}
return node;
}
static ast_node_t *parse_bracket(parser_t *parser) {
parser_advance(parser);
bracket_class_t *bracket = bracket_create();
if (!bracket) {
parser->error = LOREG_ERR_OUT_OF_MEMORY;
return NULL;
}
if (parser->current.type == TOKEN_CARET) {
bracket->negated = true;
parser_advance(parser);
}
while (parser->current.type != TOKEN_RBRACKET && parser->current.type != TOKEN_EOF) {
if (parser->current.type == TOKEN_CLASS_DIGIT ||
parser->current.type == TOKEN_CLASS_WORD ||
parser->current.type == TOKEN_CLASS_SPACE ||
parser->current.type == TOKEN_CLASS_NDIGIT ||
parser->current.type == TOKEN_CLASS_NWORD ||
parser->current.type == TOKEN_CLASS_NSPACE) {
switch (parser->current.type) {
case TOKEN_CLASS_DIGIT:
bracket_add_range(bracket, '0', '9');
break;
case TOKEN_CLASS_WORD:
bracket_add_range(bracket, 'a', 'z');
bracket_add_range(bracket, 'A', 'Z');
bracket_add_range(bracket, '0', '9');
bracket_add_char(bracket, '_');
break;
case TOKEN_CLASS_SPACE:
bracket_add_char(bracket, ' ');
bracket_add_char(bracket, '\t');
bracket_add_char(bracket, '\n');
bracket_add_char(bracket, '\r');
bracket_add_char(bracket, '\f');
bracket_add_char(bracket, '\v');
break;
default:
break;
}
parser_advance(parser);
continue;
}
char start = parser->current.value;
parser_advance(parser);
if (parser->current.type == TOKEN_DASH) {
parser_advance(parser);
if (parser->current.type == TOKEN_RBRACKET || parser->current.type == TOKEN_EOF) {
bracket_add_char(bracket, start);
bracket_add_char(bracket, '-');
} else {
char end = parser->current.value;
bracket_add_range(bracket, start, end);
parser_advance(parser);
}
} else {
bracket_add_char(bracket, start);
}
}
if (parser->current.type != TOKEN_RBRACKET) {
bracket_free(bracket);
parser->error = LOREG_ERR_INVALID_PATTERN;
return NULL;
}
parser_advance(parser);
return ast_create_bracket(bracket);
}
ast_node_t *parser_parse(parser_t *parser) {
if (parser->current.type == TOKEN_EOF) {
return NULL;
}
return parse_expr(parser);
}
+170
View File
@@ -0,0 +1,170 @@
/* retoor <retoor@molodetz.nl> */
#include "repl.h"
#include "loreg.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_INPUT 4096
static void print_banner(void) {
printf("loreg v%s - regex interpreter\n", LOREG_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(" :q quit\n");
printf(" :h show this help\n");
printf(" :p <regex> compile and set pattern\n");
printf(" :m <text> match text against pattern (anchored)\n");
printf(" :s <text> search for pattern in text\n");
printf(" <text> search for pattern in text\n\n");
printf("regex syntax:\n");
printf(" . any character\n");
printf(" * zero or more\n");
printf(" + one or more\n");
printf(" ? zero or one\n");
printf(" | alternation\n");
printf(" () grouping\n");
printf(" [] character class\n");
printf(" [^] negated class\n");
printf(" ^ start anchor\n");
printf(" $ end anchor\n");
printf(" {n} exactly n\n");
printf(" {n,} n or more\n");
printf(" {n,m} n to m\n");
printf(" \\d digit\n");
printf(" \\w word character\n");
printf(" \\s whitespace\n");
printf(" \\D \\W \\S negated classes\n\n");
}
static void print_match(const char *text, loreg_match_t *result) {
if (!result->matched) {
printf("no match\n");
return;
}
printf("match: \"");
for (size_t i = result->match_start; i < result->match_end; i++) {
printf("%c", text[i]);
}
printf("\" [%zu-%zu]\n", result->match_start, result->match_end);
for (size_t i = 0; i < result->group_count; i++) {
if (result->groups[i].matched) {
printf(" group %zu: \"", i);
for (size_t j = result->groups[i].start; j < result->groups[i].end; j++) {
printf("%c", text[j]);
}
printf("\" [%zu-%zu]\n", result->groups[i].start, result->groups[i].end);
}
}
}
static char *read_line(void) {
static char buffer[MAX_INPUT];
printf("> ");
fflush(stdout);
if (!fgets(buffer, MAX_INPUT, stdin)) {
return NULL;
}
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
return buffer;
}
void repl_run(void) {
print_banner();
loreg_regex_t *regex = NULL;
char *line;
while ((line = read_line()) != NULL) {
if (strlen(line) == 0) continue;
if (strcmp(line, ":q") == 0 || strcmp(line, ":quit") == 0) {
break;
}
if (strcmp(line, ":h") == 0 || strcmp(line, ":help") == 0) {
print_help();
continue;
}
if (strncmp(line, ":p ", 3) == 0) {
const char *pattern = line + 3;
while (*pattern == ' ') pattern++;
if (regex) {
loreg_free(regex);
regex = NULL;
}
loreg_error_t error;
regex = loreg_compile(pattern, &error);
if (!regex) {
printf("error: %s\n", loreg_error_string(error));
} else {
printf("pattern compiled: %s\n", pattern);
}
continue;
}
if (strncmp(line, ":m ", 3) == 0) {
if (!regex) {
printf("error: no pattern set (use :p <pattern>)\n");
continue;
}
const char *text = line + 3;
while (*text == ' ') text++;
loreg_match_t result;
loreg_match(regex, text, &result);
print_match(text, &result);
continue;
}
if (strncmp(line, ":s ", 3) == 0) {
if (!regex) {
printf("error: no pattern set (use :p <pattern>)\n");
continue;
}
const char *text = line + 3;
while (*text == ' ') text++;
loreg_match_t result;
loreg_search(regex, text, &result);
print_match(text, &result);
continue;
}
if (line[0] == ':') {
printf("unknown command: %s\n", line);
continue;
}
if (!regex) {
printf("error: no pattern set (use :p <pattern>)\n");
continue;
}
loreg_match_t result;
loreg_search(regex, line, &result);
print_match(line, &result);
}
if (regex) {
loreg_free(regex);
}
printf("\n");
}