Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ SUBDIR += tests/sql
SUBDIR += tests/queue
SUBDIR += tests/aho_corasick
SUBDIR += tests/retest
SUBDIR += tests/re_interpolate_groups
SUBDIR += tests/re_interpolate
SUBDIR += tests
.if make(theft) || make(${BUILD}/theft/theft)
SUBDIR += theft
Expand Down
20 changes: 18 additions & 2 deletions include/re/groups.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@

struct re_pos;

enum re_interpolate_flags {
/*
* group numbers are single-digit only. e.g. "\0123"
* means group \0 followed by literal "123".
*/
RE_INTERPOLATE_SINGLE_DIGIT = 1 << 0,

/*
* Enable \{...} syntax for groups.
* Multiple digits are permitted within the braces, regardless
* of whether RE_INTERPOLATE_SINGLE_DIGIT is set or not.
* At least one digit must be present.
*/
RE_INTERPOLATE_BRACES = 1 << 1
};

/*
* esc is the character for escaping group references,
* typically '\\' or '$'.
Expand All @@ -31,7 +47,7 @@ struct re_pos;
* You can distinguish compile-time errors (that is,
* syntax errors in the format string) vs. runtime errors
* (that is, nonexistent groups) by calling
* re_interpolate_groups() ahead of time with groupc = 0
* re_interpolate() ahead of time with groupc = 0
* and passing a non-NULL nonexistent value.
*
* The output string will always be less than or equal in
Expand All @@ -49,7 +65,7 @@ struct re_pos;
* buffer is indeterminate.
*/
bool
re_interpolate_groups(const char *fmt, char esc,
re_interpolate(const char *fmt, char esc, enum re_interpolate_flags flags,
const char *group0, unsigned groupc, const char *groupv[], const char *nonexistent,
char *outs, size_t outn,
struct re_pos *start, struct re_pos *end);
Expand Down
2 changes: 1 addition & 1 deletion src/libre/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SRC += src/libre/ast_new_from_fsm.c
SRC += src/libre/ast_rewrite.c
SRC += src/libre/ac.c
SRC += src/libre/print.c
SRC += src/libre/re_interpolate_groups.c
SRC += src/libre/re_interpolate.c
SRC += src/libre/re_strings.c

# generated
Expand Down
2 changes: 1 addition & 1 deletion src/libre/libre.syms
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ re_flags
re_strerror
re_perror
re_is_anchored
re_interpolate_groups
re_interpolate

ast_print
ast_print_dot
Expand Down
71 changes: 68 additions & 3 deletions src/libre/re_interpolate_groups.c → src/libre/re_interpolate.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
} while (0)

bool
re_interpolate_groups(const char *fmt, char esc,
re_interpolate(const char *fmt, char esc, enum re_interpolate_flags flags,
const char *group0, unsigned groupc, const char *groupv[], const char *nonexistent,
char *outs, size_t outn,
struct re_pos *start, struct re_pos *end)
Expand All @@ -43,7 +43,9 @@ re_interpolate_groups(const char *fmt, char esc,
enum {
STATE_LIT,
STATE_ESC,
STATE_DIGIT
STATE_DIGIT,
STATE_OPEN_BRACE,
STATE_INSIDE_BRACES,
} state;

assert(esc != '\0');
Expand Down Expand Up @@ -92,6 +94,15 @@ re_interpolate_groups(const char *fmt, char esc,
continue;
}

if ((flags & RE_INTERPOLATE_BRACES) && *p == '{') {
if (start != NULL) {
start->byte = p - fmt;
}

state = STATE_OPEN_BRACE;
continue;
}

if (isdigit((unsigned char) *p)) {
group = *p - '0';
state = STATE_DIGIT;
Expand All @@ -101,7 +112,7 @@ re_interpolate_groups(const char *fmt, char esc,
goto error;

case STATE_DIGIT:
if (isdigit((unsigned char) *p)) {
if (!(flags & RE_INTERPOLATE_SINGLE_DIGIT) && isdigit((unsigned char) *p)) {
group *= 10;
group += *p - '0';

Expand Down Expand Up @@ -162,6 +173,60 @@ re_interpolate_groups(const char *fmt, char esc,
OUT_CHAR(*p);
continue;

case STATE_OPEN_BRACE:
assert((flags & RE_INTERPOLATE_BRACES));

/* RE_INTERPOLATE_SINGLE_DIGIT does not apply inside braces */
if (isdigit((unsigned char) *p)) {
group = *p - '0';

/* see STATE_DIGIT */
if (group > groupc) {
group = groupc + 1;
}

state = STATE_INSIDE_BRACES;
continue;
}

/* at least one digit is required */
goto error;

case STATE_INSIDE_BRACES:
assert((flags & RE_INTERPOLATE_BRACES));

/* RE_INTERPOLATE_SINGLE_DIGIT does not apply inside braces */
if (isdigit((unsigned char) *p)) {
group *= 10;
group += *p - '0';

/* see STATE_DIGIT */
if (group > groupc) {
group = groupc + 1;
}
continue;
}

if (group == 0) {
OUT_GROUP(group0);
} else if (group <= groupc) {
assert(groupv[group - 1] != NULL);
OUT_GROUP(groupv[group - 1]);
} else if (nonexistent == NULL) {
/* see STATE_DIGIT */
goto error;
} else {
OUT_GROUP(nonexistent);
}

if (*p == '}') {
group = 0;
state = STATE_LIT;
continue;
}

goto error;

default:
assert(!"unreached");
goto error;
Expand Down
23 changes: 23 additions & 0 deletions tests/re_interpolate/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.include "../../share/mk/top.mk"

TEST.tests/re_interpolate != ls -1 tests/re_interpolate/re_interpolate*.c
TEST_SRCDIR.tests/re_interpolate = tests/re_interpolate
TEST_OUTDIR.tests/re_interpolate = ${BUILD}/tests/re_interpolate

.for n in ${TEST.tests/re_interpolate:T:R:C/^re_interpolate//}
test:: ${TEST_OUTDIR.tests/re_interpolate}/res${n}
SRC += ${TEST_SRCDIR.tests/re_interpolate}/re_interpolate${n}.c
#CFLAGS.${TEST_SRCDIR.tests/re_interpolate}/re_interpolate${n}.c = -UNDEBUG
CFLAGS.${TEST_SRCDIR.tests/re_interpolate}/re_interpolate${n}.c = -std=c99

${TEST_OUTDIR.tests/re_interpolate}/run${n}: ${TEST_OUTDIR.tests/re_interpolate}/re_interpolate${n}.o
${CC} ${CFLAGS} -o ${TEST_OUTDIR.tests/re_interpolate}/run${n} ${TEST_OUTDIR.tests/re_interpolate}/re_interpolate${n}.o ${BUILD}/src/libre/re_interpolate.o

${TEST_OUTDIR.tests/re_interpolate}/res${n}: ${TEST_OUTDIR.tests/re_interpolate}/run${n}
( ${TEST_OUTDIR.tests/re_interpolate}/run${n} 1>&2 && echo PASS || echo FAIL ) > ${TEST_OUTDIR.tests/re_interpolate}/res${n}

#.for lib in ${LIB:Mlibfsm} ${LIB:Mlibre}
#${TEST_OUTDIR.tests/re_interpolate}/run${n}: ${BUILD}/lib/${lib:R}.a
#.endfor
.endfor

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test(const char *fmt, size_t groupc, const char *groupv[], const char *expected)
assert(fmt != NULL);
assert(expected != NULL);

if (!re_interpolate_groups(fmt, '$', "<g0>", groupc, groupv, "<ne>", outs, sizeof outs, NULL, NULL)) {
if (!re_interpolate(fmt, '$', 0, "<g0>", groupc, groupv, "<ne>", outs, sizeof outs, NULL, NULL)) {
printf("%s/%zu XXX\n", fmt, groupc);
failed++;
return;
Expand All @@ -47,6 +47,10 @@ int main(void) {

test("x", 0, g0, "x");
test("x", 4, gn, "x");
test("{", 0, g0, "{");
test("{", 4, gn, "{");
test("}", 0, g0, "}");
test("}", 4, gn, "}");

test("\001", 0, g0, "\001");
test("\001", 4, gn, "\001");
Expand Down
89 changes: 89 additions & 0 deletions tests/re_interpolate/re_interpolate1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2026 Katherine Flavel
*
* See LICENCE for the full copyright terms.
*/

#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>

#include <re/re.h>
#include <re/groups.h>

static unsigned failed;

static void
test_err(const char *fmt, enum re_interpolate_flags flags,
size_t groupc, const char *groupv[], const char *ne,
unsigned expected_start, unsigned expected_end)
{
struct re_pos start, end;
char outs[10];
bool rs, re;

assert(fmt != NULL);

outs[0] = 'x';

/* for these tests we're expecting to error */
if (re_interpolate(fmt, '$', flags, "<g0>", groupc, groupv, ne, outs, sizeof outs, &start, &end)) {
printf("%s/%zu XXX\n", fmt, groupc);
failed++;
return;
}

if (outs[0] != '\0') {
failed++;
}

failed += rs = expected_start != start.byte;
failed += re = expected_end != end.byte;

printf("%s/%zu => :%u-%u :%u-%u '%.*s'%s\n", fmt, groupc,
start.byte, end.byte,
expected_start, expected_end,
(int) (end.byte - start.byte), fmt + start.byte,
(rs || re) ? " XXX" : "");
}

int main(void) {
const char *ne = "<ne>";

const char *gn[] = { "one", "two", "three", "four" };
const char **g0 = NULL;

test_err("$", 0, 0, g0, ne, 0, 1);
test_err("$x", 0, 0, g0, ne, 0, 1);
test_err("$ ", 0, 4, gn, ne, 0, 1);
test_err("$\\01", 0, 0, g0, ne, 0, 1);

test_err("$0$", 0, 0, g0, ne, 2, 3);
test_err("$$$x", 0, 4, gn, ne, 2, 3);

test_err("xyz$1", 0, 0, gn, NULL, 3, 5);
test_err("xyz$2", 0, 1, gn, NULL, 3, 5);

test_err("01234567890", 0, 1, gn, ne, 0, 10);
test_err("$$$$$$$$$$$$$$$$$$$$", 0, 1, gn, ne, 0, 20);
test_err("$1$1$1$$", 0, 1, gn, ne, 0, 8);
test_err("$1$1$1x", 0, 1, gn, ne, 0, 7);
test_err("xxxyyyzzz$$", 0, 1, gn, ne, 0, 11);

test_err("${", 0, 4, gn, ne, 0, 1);
test_err("${}", 0, 4, gn, ne, 0, 1);
test_err("${}x", 0, 4, gn, ne, 0, 1);
test_err("_${0})", 0, 4, gn, ne, 1, 2);
test_err("${1}x", 0, 4, gn, ne, 0, 1);
test_err("${1}", 0, 4, gn, ne, 0, 1);
test_err("${10}", 0, 4, gn, ne, 0, 1);
test_err("${1}1", 0, 4, gn, ne, 0, 1);

test_err("${", RE_INTERPOLATE_BRACES, 4, gn, ne, 1, 2);
test_err("${}", RE_INTERPOLATE_BRACES, 4, gn, ne, 1, 2);
test_err("${}x", RE_INTERPOLATE_BRACES, 4, gn, ne, 1, 2);

return failed;
}

69 changes: 69 additions & 0 deletions tests/re_interpolate/re_interpolate2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2026 Katherine Flavel
*
* See LICENCE for the full copyright terms.
*/

#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>

#include <re/re.h>
#include <re/groups.h>

static unsigned failed;

static void
test(const char *fmt, enum re_interpolate_flags flags, bool expected)
{
bool r;

assert(fmt != NULL);

r = re_interpolate(fmt, '$', flags, "<g0>", 0, NULL, "<ne>", NULL, 0, NULL, NULL);

failed += r != expected;

printf("%s/%d => %d%s\n", fmt, 0, r,
r != expected ? " XXX" : "");
}

int main(void) {
test("", 0, true);
test("abc", 0, true);
test("$$", 0, true);
test("{", 0, true);
test("}", 0, true);

test("$x", 0, false);
test("${}", 0, false);
test("${1}", 0, false);
test("_${0}_", 0, false);
test("_${1}_", 0, false);
test("${1", 0, false);
test("${", 0, false);
test("${$", 0, false);
test("${0$", 0, false);

test("{", RE_INTERPOLATE_BRACES, true);
test("}", RE_INTERPOLATE_BRACES, true);
test("$x", RE_INTERPOLATE_BRACES, false);
test("${}", RE_INTERPOLATE_BRACES, false);
test("${1}", RE_INTERPOLATE_BRACES, true);
test("${1}", RE_INTERPOLATE_BRACES, true);
test("_${0}_", RE_INTERPOLATE_BRACES, true);
test("_${1}_", RE_INTERPOLATE_BRACES, true);
test("${1", RE_INTERPOLATE_BRACES, false);
test("${", RE_INTERPOLATE_BRACES, false);
test("${$", RE_INTERPOLATE_BRACES, false);
test("${0$", RE_INTERPOLATE_BRACES, false);

test("${", RE_INTERPOLATE_BRACES, false);
test("${}", RE_INTERPOLATE_BRACES, false);
test("$}{", RE_INTERPOLATE_BRACES, false);
test("{$}", RE_INTERPOLATE_BRACES, false);

return failed;
}

Loading
Loading