Skip to content

Commit

Permalink
Add xmatch()
Browse files Browse the repository at this point in the history
  • Loading branch information
cpq committed Jan 2, 2025
1 parent 88c3bd9 commit 387afdd
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 3 deletions.
50 changes: 47 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ ofs = json_get(buf, len, "$.b[1]", &size); // ofs = 19, size = 1
### json\_get\_num()

```c
int mg_json_get_num(const char *buf, int len, const char *path, double *val);
int json_get_num(const char *buf, int len, const char *path, double *val);
```
Fetch numeric (double) value from the json string `buf`, `len` at JSON path
Expand Down Expand Up @@ -350,7 +350,51 @@ struct foo foo = {1, 2.34, "hi"};
xsnprintf(buf, sizeof(buf), "%M", fmt_foo, &foo);
```

## Printing to a dynamic memory
### xmatch()

```c
struct xstr {
char *buf;
size_t len;
};

bool xmatch(struct xstr str, struct xstr pattern, struct xstr *caps);
```
Check if string `str` matches glob pattern `pattern`, and optionally capture
wildcards into the provided array `caps`.
> NOTE: If `caps` is not NULL, then the `caps` array size must be at least the
> number of wildcard symbols in `pattern` plus 1. The last cap will be
> initialized to an empty string.
The glob pattern matching rules are as follows:
- `?` matches any single character
- `*` matches zero or more characters except `/`
- `#` matches zero or more characters
- any other character matches itself
Parameters:
- `str` - a string to match
- `pattern` - a pattern to match against
- `caps` - an optional array of captures for wildcard symbols `?`, `*`, '#'
Return value: `true` if matches, `false` otherwise
Usage example:
```c
// Assume that hm->uri holds /foo/bar. Then we can match the requested URI:
struct xstr caps[3]; // Two wildcard symbols '*' plus 1
struct xstr str = { "/hello/world", 12 };
struct xstr pattern = { "/*/*", 4 };
if (xmatch(str, pattern, caps)) {
// caps[0] holds `hello`, caps[1] holds `world`.
}
```

## Printing to dynamic memory

The `x*printf()` functions always return the total number of bytes that the
result string takes. Therefore it is possible to print to a `malloc()-ed`
Expand Down Expand Up @@ -382,7 +426,7 @@ used for measurements.
| Standard `snprintf` | 87476 | 81420 |
Notes:
- by default, standard snrpintf does not support float, and `x*printf` does
- by default, standard snrpintf does not support float, and `x*printf` does
- to enable float for ARM GCC (newlib), use `-u _printf_float`
- to disable float for `x*printf`, use `-DNO_FLOAT`
Expand Down
38 changes: 38 additions & 0 deletions str.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ typedef int int32_t;
#define vsnprintf _vsnprintf
#define snprintf _snprintf
#define inline __inline
typedef enum { false = 0, true = 1 } bool;
#else
#include <stdint.h>
#include <stdbool.h>
#endif

#ifdef __cplusplus
Expand All @@ -45,6 +47,8 @@ size_t fmt_b64(void (*fn)(char, void *), void *arg, va_list *ap);
size_t fmt_esc(void (*fn)(char, void *), void *arg, va_list *ap);

// Utility functions
struct xstr { char *buf; size_t len; };
bool xmatch(struct xstr s, struct xstr p, struct xstr *caps);
void xhexdump(void (*fn)(char, void *), void *arg, const void *buf, size_t len);
size_t xb64_decode(const char *src, size_t slen, char *dst, size_t dlen);

Expand Down Expand Up @@ -739,6 +743,40 @@ void xhexdump(void (*fn)(char, void *), void *a, const void *buf, size_t len) {
fn('\n', a);
}

bool xmatch(struct xstr s, struct xstr p, struct xstr *caps) {
size_t i = 0, j = 0, ni = 0, nj = 0;
if (caps) caps->buf = NULL, caps->len = 0;
while (i < p.len || j < s.len) {
if (i < p.len && j < s.len &&
(p.buf[i] == '?' ||
(p.buf[i] != '*' && p.buf[i] != '#' && s.buf[j] == p.buf[i]))) {
if (caps == NULL) {
} else if (p.buf[i] == '?') {
caps->buf = &s.buf[j], caps->len = 1; // Finalize `?` cap
caps++, caps->buf = NULL, caps->len = 0; // Init next cap
} else if (caps->buf != NULL && caps->len == 0) {
caps->len = (size_t) (&s.buf[j] - caps->buf); // Finalize current cap
caps++, caps->len = 0, caps->buf = NULL; // Init next cap
}
i++, j++;
} else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) {
if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j]; // Init cap
ni = i++, nj = j + 1;
} else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' || s.buf[j] != '/')) {
i = ni, j = nj;
if (caps && caps->buf == NULL && caps->len == 0) {
caps--, caps->len = 0; // Restart previous cap
}
} else {
return false;
}
}
if (caps && caps->buf && caps->len == 0) {
caps->len = (size_t) (&s.buf[j] - caps->buf);
}
return true;
}

#endif // STR_API_ONLY

#ifdef __cplusplus
Expand Down
21 changes: 21 additions & 0 deletions test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,33 @@ static void test_base64(void) {
assert(strcmp(b, "hi") == 0);
}

static struct xstr xstr_n(const char *s, size_t n) {
struct xstr str = {(char *) s, n};
return str;
}

static struct xstr xstr(const char *s) {
struct xstr str = {(char *) s, strlen(s)};
return str;
}

static void test_xmatch(void) {
struct xstr caps[3];
assert(xmatch(xstr_n("", 0), xstr_n("", 0), NULL) == true);
assert(xmatch(xstr("//a.c"), xstr("#.c"), NULL) == true);
assert(xmatch(xstr("a"), xstr("#"), caps) == true);
assert(xmatch(xstr("//a.c"), xstr("#.c"), caps) == true);
assert(xmatch(xstr("a_b_c_"), xstr("a*b*c"), caps) == false);
assert(xmatch(xstr("a__b_c"), xstr("a*b*c"), caps) == true);
}

int main(void) {
test_std();
test_float();
test_m();
test_json();
test_base64();
test_xmatch();
printf("SUCCESS\n");
return 0;
}

0 comments on commit 387afdd

Please sign in to comment.