diff options
| author | Tyge Løvset <[email protected]> | 2022-07-28 00:24:13 +0200 |
|---|---|---|
| committer | Tyge Løvset <[email protected]> | 2022-07-28 12:39:34 +0200 |
| commit | 281a4148d9143260c614c92e81d3484103479761 (patch) | |
| tree | 6661f8f41c416c0120d677fbaffb07f9b3eabbc9 | |
| parent | 18392a1f49bb742fce3e28bb8196b2a64ea07219 (diff) | |
| download | STC-modified-281a4148d9143260c614c92e81d3484103479761.tar.gz STC-modified-281a4148d9143260c614c92e81d3484103479761.zip | |
VERSION 3.8 RC. Added cregex with "final" API + docs. README.md updated with links to cregex, coption.
crandom.h: fixed "stc64_with_seq doesn't use seq argument #31" thanks to funny-falcon. Removed deprecated funcs.
Added tags for v3.6 and v3.7.
| -rw-r--r-- | README.md | 24 | ||||
| -rw-r--r-- | docs/cregex_api.md | 183 | ||||
| -rw-r--r-- | examples/regex_replace.c | 19 | ||||
| -rw-r--r-- | include/stc/crandom.h | 11 | ||||
| -rw-r--r-- | include/stc/cregex.h | 20 | ||||
| -rw-r--r-- | src/cregex.c | 13 |
6 files changed, 231 insertions, 39 deletions
@@ -5,7 +5,10 @@ STC - Smart Template Containers for C News: Version 3.8 released (July 2022) --------------------------------------- -- [See changes version 3](#version-3). Note some code-breaking API changes! +- "Officially" added **cregex** - powerful regular expressions. +- Added back **coption** - command line argument parsing. +- Some changes in **cstr** and **csview** API. +- [See detailed changes for version 3](#version-3). Introduction ------------ @@ -41,10 +44,9 @@ The library is mature and well tested, so you may use it in projects. However, m still happen. The main development of this project is finished, but I will handle PRs with bugs and improvements in the future, and do minor modifications. -Contents --------- -- [***ccommon*** - RAII and iterator macros](docs/ccommon_api.md) -- [***carc*** - **std::shared_ptr** alike support](docs/carc_api.md) +Containers +---------- +- [***carc*** - **std::shared_ptr** alike type](docs/carc_api.md) - [***carr2***, ***carr3*** - 2D and 3D **array** types](docs/carray_api.md) - [***cbits*** - **std::bitset** alike type](docs/cbits_api.md) - [***cbox*** - **std::unique_ptr** alike type](docs/cbox_api.md) @@ -53,7 +55,6 @@ Contents - [***cmap*** - **std::unordered_map** alike type](docs/cmap_api.md) - [***cpque*** - **std::priority_queue** alike type](docs/cpque_api.md) - [***cqueue*** - **std::queue** alike type](docs/cqueue_api.md) -- [***crandom*** - A novel very fast *PRNG* named **stc64**](docs/crandom_api.md) - [***cset*** - **std::unordered_set** alike type](docs/cset_api.md) - [***csmap*** - **std::map** sorted map alike type](docs/csmap_api.md) - [***csset*** - **std::set** sorted set alike type](docs/csset_api.md) @@ -62,6 +63,13 @@ Contents - [***csview*** - **std::string_view** alike type](docs/csview_api.md) - [***cvec*** - **std::vector** alike type](docs/cvec_api.md) +Others +------ +- [***ccommon*** - RAII and iterator macros](docs/ccommon_api.md) +- [***coption*** - getopt() alike command line args parser](docs/coption_api.md) +- [***crandom*** - A novel very fast *PRNG* named **stc64**](docs/crandom_api.md) +- [***cregex*** - Regular expression parser (extended from Rob Pike's regexp9)](docs/cregex_api.md) + Highlights ---------- - **User friendly** - Just include the headers and you are good. The API and functionality is very close to c++ STL, and is fully listed in the docs. @@ -467,7 +475,9 @@ Memory efficiency - Added cstr_u8_slice() and csview_u8_slice(). - Removed `csview_from_s()`: Use `cstr_sv(s)` instead. - Removed `csview_from_n()`: Use `c_sv(str, n)` instead. - - Updated cstr and csview docs. + - Added back file coption.h + - Simplified **cbits** usage: all inlined. + - Updated docs. ## Changes version 3.7 - NB! Changed self argument from value to const pointer on containers (does not apply to **cstr**): diff --git a/docs/cregex_api.md b/docs/cregex_api.md new file mode 100644 index 00000000..d04bb32e --- /dev/null +++ b/docs/cregex_api.md @@ -0,0 +1,183 @@ +# STC [cregex](../include/stc/cregex.h): Regular Expressions + + +## Description + +**cregex** is a small and fast unicode UTF8 regular expression parser. It is based on Rob Pike's non-backtracking NFA-based regular expression implementation for the Plan 9 project. See Russ Cox's articles [Implementing Regular Expressions](https://swtch.com/~rsc/regexp/) on why NFA-based regular expression engines often are superiour to the common backtracking implementations (hint: NFAs have no "bad/slow" RE patterns). + +The API is simple and includes powerful string pattern matches and replace functions. See example below and in the example folder. + +## Methods + +```c +enum { + // compile-flags + cre_c_dotall = 1<<0, // dot matches newline too + cre_c_caseless = 1<<1, // ignore case + // match-flags + cre_m_fullmatch = 1<<2, // like start-, end-of-line anchors were in pattern: "^ ... $" + cre_m_next = 1<<3, // use end of previous match[0] as start of input + cre_m_startend = 1<<4, // use match[0] as start+end of input + // replace-flags + cre_r_strip = 1<<5, // only keep the matched strings, strip the rest +}; + +cregex cregex_init(void); + +cregex cregex_from(const char* pattern, int cflags); + // return 1 = success, negative = error. +int cregex_compile(cregex *self, const char* pattern, int cflags); + + // num. of capture groups in regex. 0 if RE is invalid. First group is the full match. +int cregex_captures(const cregex* self); + + // return 1=match, 0=nomatch, -1=error. match array size: at least num groups in RE (1+). +int cregex_find(const char* input, const cregex* re, csview match[], int mflags); + // takes string pattern instead of re. (for one-time matches) +int cregex_find_p(const char* input, const char* pattern, csview match[], int cmflags); + +bool cregex_is_match(const char* input, const cregex* re, int mflags); + +cstr cregex_replace(const char* input, const cregex* re, const char* replace); +cstr cregex_replace_re(const char* input, const cregex* re, const char* replace, // extended args: + bool (*mfun)(int grp, csview match, cstr* mstr), unsigned count, int rflags); + // takes string pattern instead of re +cstr cregex_replace_p(const char* input, const char* pattern, const char* replace); +cstr cregex_replace_pe(const char* input, const char* pattern, const char* replace, + bool (*mfun)(int grp, csview match, cstr* mstr), unsigned count, int crflags); + +void cregex_drop(cregex* self); // destroy +``` + +### Error codes + +- cre_success = 1 +- cre_nomatch = 0 +- cre_matcherror = -1 +- cre_outofmemory = -2 +- cre_unmatchedleftparenthesis = -3 +- cre_unmatchedrightparenthesis = -4 +- cre_toomanysubexpressions = -5 +- cre_toomanycharacterclasses = -6 +- cre_malformedcharacterclass = -7 +- cre_missingoperand = -8 +- cre_unknownoperator = -9 +- cre_operandstackoverflow = -10 +- cre_operatorstackoverflow = -11 +- cre_operatorstackunderflow = -12 + +### Limits +- cre_MAXCLASSES +- cre_MAXCAPTURES + +## Usage + +### Compiling a regular expression +```c +cregex re1 = cregex_init(); +int result = cregex_compile(&re1, "[0-9]+", 0); +if (result < 0) return result; + +const char* url = "(https?://|ftp://|www\\.)([0-9A-Za-z@:%_+~#=-]+\\.)+([a-z][a-z][a-z]?)(/[/0-9A-Za-z\\.@:%_+~#=\\?&-]*)?"; +cregex re2 = cregex_from(url, 0); +if (re2.error) return re2.error; +... +cregex_drop(&re2); +cregex_drop(&re1); +``` +If an error occurs ```cregex_compile``` returns a negative value, see error codes. + +### Getting the first match +```c +#define i_implement +#include <stc/cstr.h> +#include <stc/cregex.h> + +int main() { + const char* input = "start date is 2023-03-01, and end date is 2025-12-31."; + const char* pattern = "\\b(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)\\b"; + + cregex re = cregex_from(pattern, 0); + + // Lets find the first date in the string: + csview match[4]; // full-match, year, month, date. + if (cregex_find(input, &re, match, 0) == cre_success) + printf("Found date: %.*s\n", c_ARGsv(match[0])); + else + printf("Could not find any date\n"); + + // Lets change all dates into US date format MM/DD/YYYY: + cstr us_input = cregex_replace(input, &re, "$2/$3/$1"); + printf("US input: %s\n", cstr_str(&us_input)); + + // Free allocated data + cstr_drop(&us_input); + cregex_drop(&re); +} +``` +To compile, use: `gcc first_match.c src/cregex.c src/utf8code.c`. +For a single match you may use the all-in-one function: +```c +if (cregex_find_p(input, pattern, match, 0)) + printf("Found date: %.*s\n", c_ARGsv(match[0])); +``` + +## Using cregex in a project +**cregex** uses the following files: +- `stc/cregex.h`, `stc/utf8.h`, `stc/csview.h`, `stc/cstr.h`, `stc/ccommon.h`, `stc/forward.h` +- `src/cregex.c`, `src/utf8code.c`. + +## Regex Cheatsheet + +| Metacharacter | Description | STC addition | +|:--:|:--:|:--:| +| c | Most characters (like c) match themselve literally | | +| \c | Some characters are used as metacharacters. To use them literally escape them | | +| . | Match any character, except newline unless in (?s) mode | | +| ? | Match the preceding token zero or one time | | +| * | Match the preceding token as often as possible | | +| + | Match the preceding token at least once and as often as possible | | +| \| | Match either the expression before the \| or the expression after it | | +| (c) | Match the expression inside the parentheses. This adds a capture group | | +| [c] | Match all characters inside the brackets. Ranges like a-z may also be used | | +| [^c] | Do not match the characters inside the bracket. | | +| \x{***hex***} | Match character given as hex number | * | +| ^ | Start of line anchor | | +| $ | End of line anchor | | +| \A | Start of input anchor | * | +| \Z | End of input anchor | * | +| \z | End of input including optional newline | * | +| \b | UTF8 word boundary anchor | * | +| \B | Not UTF8 word boundary | * | +| \Q | Start literal input mode | * | +| \E | End literal input mode | * | +| (?i) (?-i) | Ignore case on/off (override global) | * | +| (?s) (?-s) | Dot matches newline on/off (override global) | * | +| \n \t \r | Match UTF8 newline, tab, carriage return | | +| \d \s \w | Match UTF8 digit, whitespace, alphanumeric character | | +| \D \S \W | Do not match the groups described above | | +| \p{Space} or \p{Sz} | Match UTF8 whitespace | * | +| \p{Digit} or \p{Nd} | Match UTF8 numeric | * | +| \p{XDigit} | Match UTF8 hex number | * | +| \p{Lower} or \p{Ll} | Match UTF8 lower case | * | +| \p{Upper} or \p{Lu} | Match UTF8 upper case | * | +| \p{Alpha} or \p{LC} | Match UTF8 cased letter | * | +| \p{Alnum} | Match UTF8 alpha numeric | * | +| \P{*class*} | Do not match the classes described above | * | +| [[:alnum:]] [[:alpha:]] [[:ascii:]] | Match ASCII character class | * | +| [[:blank:]] [[:cntrl:]] [[:digit:]] | Match ASCII character class | * | +| [[:graph:]] [[:lower:]] [[:print:]] | Match ASCII character class | * | +| [[:punct:]] [[:space:]] [[:upper:]] | Match ASCII character class | * | +| [[:xdigit:]] [[:word:]] | Match ASCII character class | * | +| [[:^\<class\>:]] | Do not match ASCII character class | * | +| $***n*** | *n*-th substitution backreference to capture group. *n* in 0-9. $0 is the entire match. + +## Limitations + +The main goal of **cregex** is to be small and fast with limited but useful unicode support. In order to reach these goals, **cregex** currently does not support the following features (non-exhaustive list): +- In order to limit table sizes, most general UTF8 character classes are missing, like \p{L}, \p{S}, and all specific scripts like \p{Greek} etc. Some/all of these may be added in the future as an alternative source file with unicode tables to link with. +- {n, m} syntax for repeating previous token min-max times. +- Non-capturing groups +- Lookaround and backreferences + +If you need a more feature complete, but bigger library, use [RE2 with C-wrapper](https://github.com/google/re2) which uses the same type of regex engine as **cregex**, or use [PCRE2](https://www.pcre.org/).
\ No newline at end of file diff --git a/examples/regex_replace.c b/examples/regex_replace.c index 3dcb965d..35b3c696 100644 --- a/examples/regex_replace.c +++ b/examples/regex_replace.c @@ -33,13 +33,18 @@ int main() cstr_take(&str, cregex_replace_pe(input, pattern, "[$0]", NULL, 1, 0)); printf("brack: %s\n", cstr_str(&str)); - /* European date format. Show how to compile RE separately */ - cregex re = cregex_from(pattern, 0); - if (cregex_captures(&re) == 0) - continue; - cstr_take(&str, cregex_replace(input, &re, "$3.$2.$1")); - cregex_drop(&re); - printf("euros: %s\n", cstr_str(&str)); + /* Shows how to compile RE separately */ + c_autovar (cregex re = cregex_from(pattern, 0), cregex_drop(&re)) { + if (cregex_captures(&re) == 0) + continue; + /* European date format. */ + cstr_take(&str, cregex_replace(input, &re, "$3.$2.$1")); + printf("euros: %s\n", cstr_str(&str)); + + /* Strip out everything but the matches */ + cstr_take(&str, cregex_replace_re(input, &re, "$3.$2.$1;", NULL, 0, cre_r_strip)); + printf("strip: %s\n", cstr_str(&str)); + } /* Wrap all words in ${} */ cstr_take(&str, cregex_replace_p("[52] apples and [31] mangoes", "[a-z]+", "$${$0}")); diff --git a/include/stc/crandom.h b/include/stc/crandom.h index 06964a5d..0e34e850 100644 --- a/include/stc/crandom.h +++ b/include/stc/crandom.h @@ -108,15 +108,6 @@ STC_INLINE stc64_normalf_t stc64_normalf_new(double mean, double stddev) { return c_make(stc64_normalf_t){mean, stddev, 0.0, 0}; } -/* Following functions are deprecated (will be removed in the future): */ -STC_INLINE void stc64_srandom(uint64_t seed) { csrandom(seed); } -STC_INLINE uint64_t stc64_random() { return crandom(); } -STC_INLINE stc64_t stc64_init(uint64_t seed) { return stc64_new(seed); } -STC_INLINE stc64_uniformf_t stc64_uniformf_init(double low, double high) - { return stc64_uniformf_new(low, high); } -STC_INLINE stc64_normalf_t stc64_normalf_init(double mean, double stddev) - { return stc64_normalf_new(mean, stddev); } - /* -------------------------- IMPLEMENTATION ------------------------- */ #if defined(i_implement) || defined(i_extern) @@ -142,7 +133,7 @@ STC_DEF double crandomf(void) { /* rng.state[4] must be odd */ STC_DEF stc64_t stc64_with_seq(uint64_t seed, uint64_t seq) { stc64_t rng = {{seed+0x26aa069ea2fb1a4d, seed+0x70c72c95cd592d04, - seed+0x504f333d3aa0b359, seed, seed<<1 | 1}}; + seed+0x504f333d3aa0b359, seed, seq<<1 | 1}}; for (int i = 0; i < 6; ++i) stc64_rand(&rng); return rng; } diff --git a/include/stc/cregex.h b/include/stc/cregex.h index c920ae7b..4247197c 100644 --- a/include/stc/cregex.h +++ b/include/stc/cregex.h @@ -1,6 +1,5 @@ /* This is a Unix port of the Plan 9 regular expression library, by Rob Pike. -Please send comments about the packaging to Russ Cox <[email protected]>. Copyright © 2021 Plan 9 Foundation Copyright © 2022 Tyge Løvset, for additions made in 2022. @@ -31,6 +30,7 @@ THE SOFTWARE. * This is a extended version of regexp9, supporting UTF8 input, common * shorthand character classes, ++. */ +#include <stdbool.h> #include "forward.h" // csview typedef enum { @@ -52,12 +52,14 @@ typedef enum { enum { /* compile-flags */ - cre_c_dotall = 1<<0, - cre_c_caseless = 1<<1, + cre_c_dotall = 1<<0, /* dot matches newline too */ + cre_c_caseless = 1<<1, /* ignore case */ /* match-flags */ - cre_m_fullmatch = 1<<2, - cre_m_next = 1<<3, - cre_m_startend = 1<<4, + cre_m_fullmatch = 1<<2, /* like start-, end-of-line anchors were in pattern: "^ ... $" */ + cre_m_next = 1<<3, /* use end of previous match[0] as start of input */ + cre_m_startend = 1<<4, /* use match[0] as start+end of input */ + /* replace-flags */ + cre_r_strip = 1<<5, /* only keep the matched strings, strip rest */ /* limits */ cre_MAXCLASSES = 16, cre_MAXCAPTURES = 32, @@ -103,14 +105,14 @@ bool cregex_is_match(const char* input, const cregex* re, int mflags) /* replace regular expression */ cstr cregex_replace_re(const char* input, const cregex* re, const char* replace, - bool (*mfun)(int i, csview match, cstr* mstr), unsigned count); + bool (*mfun)(int i, csview match, cstr* mstr), unsigned count, int rflags); static inline cstr cregex_replace(const char* input, const cregex* re, const char* replace) - { return cregex_replace_re(input, re, replace, NULL, 0); } + { return cregex_replace_re(input, re, replace, NULL, 0, 0); } /* replace + compile RE pattern, and extra arguments */ cstr cregex_replace_pe(const char* input, const char* pattern, const char* replace, - bool (*mfun)(int i, csview match, cstr* mstr), unsigned count, int cflags); + bool (*mfun)(int i, csview match, cstr* mstr), unsigned count, int crflags); static inline cstr cregex_replace_p(const char* input, const char* pattern, const char* replace) { return cregex_replace_pe(input, pattern, replace, NULL, 0, 0); } diff --git a/src/cregex.c b/src/cregex.c index 1d9609b3..c50e870f 100644 --- a/src/cregex.c +++ b/src/cregex.c @@ -1228,34 +1228,35 @@ int cregex_find_p(const char* input, const char* pattern, cstr cregex_replace_re(const char* input, const cregex* re, const char* replace, - bool (*mfun)(int i, csview match, cstr* mstr), unsigned count) { + bool (*mfun)(int i, csview match, cstr* mstr), unsigned count, int rflags) { cstr out = cstr_null; cstr subst = cstr_null; size_t from = 0; csview match[cre_MAXCAPTURES]; unsigned nmatch = cregex_captures(re); if (!count) count = ~0; + bool copy = !(rflags & cre_r_strip); while (count-- && cregex_find(input + from, re, match, 0) == 1) { build_subst_string(replace, nmatch, match, mfun, &subst); const size_t pos = match[0].str - input; - cstr_append_n(&out, input + from, pos - from); + if (copy) cstr_append_n(&out, input + from, pos - from); cstr_append_s(&out, subst); from = pos + match[0].size; } - cstr_append(&out, input + from); + if (copy) cstr_append(&out, input + from); cstr_drop(&subst); return out; } cstr cregex_replace_pe(const char* input, const char* pattern, const char* replace, - bool (*mfun)(int i, csview match, cstr* mstr), unsigned count, int cflags) { + bool (*mfun)(int i, csview match, cstr* mstr), unsigned count, int crflags) { cregex re = cregex_init(); - int res = cregex_compile(&re, pattern, cflags); + int res = cregex_compile(&re, pattern, crflags); if (res < 0) return cstr_new("[[error: invalid regex pattern]]"); - cstr out = cregex_replace_re(input, &re, replace, mfun, count); + cstr out = cregex_replace_re(input, &re, replace, mfun, count, crflags); cregex_drop(&re); return out; } |
