[Issue #24] Merge branch 'features/extglob' into bleeding
This commit is contained in:
commit
b881d85b19
4 changed files with 272 additions and 2 deletions
|
@ -15,6 +15,7 @@ libcriterion_la_CFLAGS = \
|
|||
$(COVERAGE_CFLAGS)
|
||||
|
||||
libcriterion_la_LDFLAGS = $(COVERAGE_LDFLAGS) -no-undefined -version-info 1:0:0
|
||||
libcriterion_la_LIBADD = -lpcre
|
||||
|
||||
EXTRA_DIST = config.rpath LICENSE
|
||||
|
||||
|
@ -55,6 +56,8 @@ libcriterion_la_SOURCES = \
|
|||
src/i18n.h \
|
||||
src/ordered-set.c \
|
||||
src/posix-compat.c \
|
||||
src/extmatch.c \
|
||||
src/extmatch.h \
|
||||
src/main.c
|
||||
|
||||
TARGET = $(PACKAGE)-$(VERSION)
|
||||
|
|
255
src/extmatch.c
Normal file
255
src/extmatch.c
Normal file
|
@ -0,0 +1,255 @@
|
|||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <pcre.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include "criterion/common.h"
|
||||
|
||||
struct context {
|
||||
int depth;
|
||||
char *dst;
|
||||
const char *src;
|
||||
char old, cur;
|
||||
int eos;
|
||||
const char **errmsg;
|
||||
jmp_buf jmp;
|
||||
};
|
||||
|
||||
void transform_impl(struct context *ctx);
|
||||
|
||||
static inline void transform_rec(struct context *ctx) {
|
||||
struct context new_ctx = {
|
||||
.depth = ctx->depth + 1,
|
||||
.dst = ctx->dst,
|
||||
.src = ctx->src,
|
||||
.old = ctx->old,
|
||||
.eos = ctx->eos,
|
||||
};
|
||||
transform_impl(&new_ctx);
|
||||
ctx->dst = new_ctx.dst;
|
||||
ctx->src = new_ctx.src;
|
||||
ctx->old = new_ctx.old;
|
||||
ctx->eos = new_ctx.eos;
|
||||
}
|
||||
|
||||
static inline char read_char(struct context *ctx) {
|
||||
char c = *ctx->src;
|
||||
ctx->old = ctx->cur;
|
||||
ctx->cur = c;
|
||||
if (c == '\0')
|
||||
ctx->eos = 1;
|
||||
++ctx->src;
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline char peek_char(struct context *ctx) {
|
||||
return *ctx->src;
|
||||
}
|
||||
|
||||
static inline void copy_char(struct context *ctx, char src) {
|
||||
*ctx->dst = src;
|
||||
++ctx->dst;
|
||||
}
|
||||
|
||||
static inline void dup_char(struct context *ctx) {
|
||||
copy_char(ctx, read_char(ctx));
|
||||
}
|
||||
|
||||
static inline void copy_str(struct context *ctx, const char *src) {
|
||||
size_t len = strlen(src);
|
||||
strncpy(ctx->dst, src, len);
|
||||
ctx->dst += len;
|
||||
}
|
||||
|
||||
#define PREPREFIX 0
|
||||
#define POSTPREFIX 1
|
||||
#define PRESUFFIX 2
|
||||
#define POSTSUFFIX 3
|
||||
#define ELSESTR 4
|
||||
|
||||
typedef struct {
|
||||
int (*validator)(struct context *ctx);
|
||||
char *str;
|
||||
} handler_arg;
|
||||
|
||||
static int active() { return 1; }
|
||||
static int inactive() { return 0; }
|
||||
|
||||
static int is_eos(struct context *ctx) {
|
||||
return peek_char(ctx) == '\0';
|
||||
}
|
||||
|
||||
static inline void handle_special(struct context *ctx, handler_arg strs[5]) {
|
||||
if (peek_char(ctx) == '(') {
|
||||
if ((strs[0].validator ?: inactive)(ctx))
|
||||
copy_str(ctx, strs[0].str);
|
||||
dup_char(ctx);
|
||||
if ((strs[1].validator ?: inactive)(ctx))
|
||||
copy_str(ctx, strs[1].str);
|
||||
|
||||
transform_rec(ctx);
|
||||
|
||||
if ((strs[2].validator ?: inactive)(ctx))
|
||||
copy_str(ctx,strs[2].str);
|
||||
copy_char(ctx, ')');
|
||||
if ((strs[3].validator ?: inactive)(ctx))
|
||||
copy_str(ctx, strs[3].str);
|
||||
} else if ((strs[4].validator ?: inactive)(ctx)) {
|
||||
copy_str(ctx, strs[4].str);
|
||||
}
|
||||
}
|
||||
|
||||
# define Handler(Name, ...) \
|
||||
static void Name(struct context *ctx, UNUSED char c) { \
|
||||
handle_special(ctx, (handler_arg[5]) { \
|
||||
__VA_ARGS__ \
|
||||
}); \
|
||||
}
|
||||
|
||||
# define _ active
|
||||
Handler(handle_plus, [POSTSUFFIX] = {_, "+"}, [ELSESTR] = {_, "+" });
|
||||
Handler(handle_star, [POSTSUFFIX] = {_, "*"}, [ELSESTR] = {_, ".*"});
|
||||
Handler(handle_wild, [POSTSUFFIX] = {_, "?"}, [ELSESTR] = {_, "." });
|
||||
Handler(handle_excl,
|
||||
[POSTPREFIX] = {_, "?!"},
|
||||
[PRESUFFIX] = {is_eos, "$" },
|
||||
[POSTSUFFIX] = {_, ".*"},
|
||||
[ELSESTR] = {_, "!" }
|
||||
);
|
||||
Handler(handle_at, [ELSESTR] = {_, "@"});
|
||||
# undef _
|
||||
|
||||
static void handle_rbra(struct context *ctx, UNUSED char c) {
|
||||
copy_char(ctx, c);
|
||||
if (peek_char(ctx) == '!') {
|
||||
read_char(ctx);
|
||||
copy_char(ctx, '^');
|
||||
}
|
||||
}
|
||||
|
||||
static void escape_char(struct context *ctx, char c) {
|
||||
copy_char(ctx, '\\');
|
||||
copy_char(ctx, c);
|
||||
}
|
||||
|
||||
static void escape_pipe(struct context *ctx, UNUSED char c) {
|
||||
if (ctx->depth == 0)
|
||||
copy_char(ctx, '\\');
|
||||
copy_char(ctx, '|');
|
||||
}
|
||||
|
||||
typedef void (*f_handler)(struct context *, char);
|
||||
|
||||
void transform_impl(struct context *ctx) {
|
||||
static f_handler handlers[] = {
|
||||
['+'] = handle_plus,
|
||||
['*'] = handle_star,
|
||||
['?'] = handle_wild,
|
||||
['!'] = handle_excl,
|
||||
['['] = handle_rbra,
|
||||
['@'] = handle_at,
|
||||
|
||||
['.'] = escape_char,
|
||||
['('] = escape_char,
|
||||
[')'] = escape_char,
|
||||
['|'] = escape_pipe,
|
||||
};
|
||||
for (char c = read_char(ctx); !ctx->eos; c = read_char(ctx)) {
|
||||
f_handler handler = handlers[(unsigned char) c];
|
||||
|
||||
if (ctx->old == '\\')
|
||||
handler = copy_char;
|
||||
|
||||
if (c == ')' && ctx->depth > 0)
|
||||
return;
|
||||
|
||||
(handler ?: copy_char)(ctx, c);
|
||||
|
||||
if (ctx->eos)
|
||||
return;
|
||||
}
|
||||
if (ctx->depth > 0) {
|
||||
*ctx->errmsg = "mismatching parenthesis";
|
||||
longjmp(ctx->jmp, -1); // abort operation
|
||||
}
|
||||
}
|
||||
|
||||
static int transform(const char *pattern, char *result, const char **errmsg) {
|
||||
struct context ctx = {
|
||||
.src = pattern,
|
||||
.dst = result,
|
||||
.errmsg = errmsg,
|
||||
};
|
||||
if (!setjmp(ctx.jmp)) {
|
||||
copy_char(&ctx, '^');
|
||||
transform_impl(&ctx);
|
||||
copy_char(&ctx, '$');
|
||||
copy_char(&ctx, '\0');
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* let T be the transformation function,
|
||||
* let diff be the function that yields the greatest character difference
|
||||
* before and after its parameter has been transformed by T.
|
||||
*
|
||||
* T('*') = '.*'; diff('*') = 1
|
||||
* T('!()') = '(?!).*' or '(?!$).*'; diff('!()') = 4
|
||||
* T('@') = '' or '@'; diff('@') = 0
|
||||
* T('|') = '|' or '\|'; diff('|') = 1
|
||||
* T('.') = '\.'; diff('.') = 1
|
||||
* T('(') = '\('; diff('(') = 1
|
||||
* T(')') = '\)'; diff(')') = 1
|
||||
* for every other 1 character string s, we have T(s) = s; hence diff(s) = 0
|
||||
*
|
||||
* let num be the function that yields the number of occurences of a string.
|
||||
* let spec be the set {(s, num(s)) | ∀s}
|
||||
* ∀s, lenght(T(s)) = length(s) + Σ((c_i, n_i) ∈ spec, n_i * diff(c_i))
|
||||
*
|
||||
* let S = {'*', '!()', '|', '.', '(', ')'}.
|
||||
* since ∀s ∉ S, diff(s) = 0, we can simplify the above equation as:
|
||||
*
|
||||
* ∀s, lenght(T(s)) = length(s) + num('*') + num('|') + num('.')
|
||||
* + num('(') + num(')') + 4 * num('!()').
|
||||
*
|
||||
* We must now find the maximal length L such as ∀s, L >= length(T(s))
|
||||
*
|
||||
* It is immediately apparent that the largest string will depend on the number
|
||||
* of occurrences of '!()'. Hence, let u be a string that is a repeating
|
||||
* sequence of '!()' padded by '.' to a multiple of 3,
|
||||
*
|
||||
* let N = floor(length(u) / 3),
|
||||
* let Q = length(u) mod 3,
|
||||
* hence num('!()') = N.
|
||||
*
|
||||
* ∀s | lenght(s) = length(u),
|
||||
* length(T(s)) <= length(T(u))
|
||||
* <= length(u) | the original length
|
||||
* + 4 * N | the expansion of all '!()'
|
||||
* + Q * diff('.') | the expansion of Q '.'
|
||||
* <= 3 * N + Q + 4 * N + Q
|
||||
* <= 7 * N + 4
|
||||
* <= 7 * floor(length(u) / 3) + 4
|
||||
* <= 7 * floor(length(s) / 3) + 4
|
||||
*
|
||||
*/
|
||||
static inline size_t max_length(size_t len) {
|
||||
return 7 * len / 3 + 4;
|
||||
}
|
||||
|
||||
int extmatch(const char *pattern, const char *string, const char **errmsg) {
|
||||
char regex[max_length(strlen(pattern))];
|
||||
if (transform(pattern, regex, errmsg) != -1) {
|
||||
int erroffset;
|
||||
pcre *preg = pcre_compile(regex, 0, errmsg, &erroffset, NULL);
|
||||
if (preg) {
|
||||
int res = pcre_exec(preg, NULL, string, strlen(string), 0, 0, NULL, 0);
|
||||
pcre_free(preg);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return -10;
|
||||
}
|
6
src/extmatch.h
Normal file
6
src/extmatch.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef EXTMATCH_H_
|
||||
# define EXTMATCH_H_
|
||||
|
||||
int extmatch(const char *pattern, const char *string, const char **errmsg);
|
||||
|
||||
#endif /* !EXTMATCH_H_ */
|
10
src/report.c
10
src/report.c
|
@ -33,7 +33,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_FNMATCH
|
||||
#include <fnmatch.h>
|
||||
#include "extmatch.h"
|
||||
#endif
|
||||
|
||||
#define IMPL_CALL_REPORT_HOOKS(Kind) \
|
||||
|
@ -60,8 +60,14 @@ void disable_unmatching(struct criterion_test_set *set) {
|
|||
continue;
|
||||
|
||||
FOREACH_SET(struct criterion_test *test, s->tests) {
|
||||
if (fnmatch(criterion_options.pattern, test->data->identifier_, 0))
|
||||
const char *errmsg;
|
||||
int ret = extmatch(criterion_options.pattern, test->data->identifier_, &errmsg);
|
||||
if (ret == -10) {
|
||||
printf("pattern error: %s\n", errmsg);
|
||||
exit(1);
|
||||
} else if (ret < 0) {
|
||||
test->data->disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue