├── LICENSE ├── match.c ├── match.h ├── readme.md └── test.c /LICENSE: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | Copyright (c) 2015 Stephan Brumme 4 | 5 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 6 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 7 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. 8 | If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 9 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 10 | 3. This notice may not be removed or altered from any source distribution. 11 | -------------------------------------------------------------------------------- /match.c: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // match.c 3 | // Copyright (c) 2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | // I improved Rob Pike's code found in the excellent book "Beautiful Code", ISBN 9780596510046 8 | // http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html 9 | // additions (already mentioned in his extension proposals): 10 | // - match a symbol which is repeated at least once "x+" (which is the same as "xx*") 11 | // - match a symbol which appears at most once "x?" 12 | // - regex can contain escaped metasymbols => "\." searches for a dot 13 | // - matchlines splits input into lines 14 | 15 | #include "match.h" 16 | 17 | // we have to define NULL because we don't include string.h directly or indirectly 18 | #define NULL 0 19 | 20 | // track escaped characters 21 | #define YES 1 22 | #define NO 0 23 | 24 | // Windows uses \r\n for newlines, Unix only \n 25 | #define PROCESS_CRLF 1 26 | 27 | 28 | /// matchhere: search for regex only at beginning of text, stop at textEnd or when a zero-byte is found 29 | /** local helper function for match(): 30 | @param text - text to be matched 31 | @param textEnd - last byte of text to be considered, can be NULL if text is zero-terminated 32 | @param regex - simplified regular expression as explained in the header file 33 | @param escaped - YES treats first character of regex as a literal only and ignores their meta symbol meaning, but NO is default 34 | @return if successful points beyond last matching literal, else NULL 35 | **/ 36 | static const char* matchhere(const char* text, const char* textEnd, const char* regex, int escaped) 37 | { 38 | // process current byte 39 | if (escaped == NO) 40 | { 41 | switch (*regex) 42 | { 43 | case 0: // regex empty ? 44 | return text; 45 | case '\\': // treat next character as a literal, not a metasymbol 46 | return matchhere(text, textEnd, regex + 1, YES); 47 | case '^': // ^ was already handled in match() / matchlines() 48 | return NULL; 49 | } 50 | } 51 | 52 | // lookahead one byte (i.e. watch out for meta-symbols) 53 | switch (regex[1]) 54 | { 55 | case '+': // symbol appears at least once 56 | while (*regex == *text || // keep running if next symbol matches 57 | (*regex == '.' && escaped == NO && text != textEnd && *text != 0)) 58 | { 59 | // match remainder 60 | const char* matched = matchhere(++text, textEnd, regex + 2, NO); 61 | if (matched != NULL) 62 | return matched; 63 | } 64 | 65 | // running out of text 66 | break; // failed 67 | 68 | case '*': // symbol appears arbitrarily often 69 | // eat text symbol-by-symbol and try to match remaining regex 70 | do 71 | { 72 | // match remaining regex 73 | const char* matched = matchhere(text, textEnd, regex + 2, NO); 74 | if (matched != NULL) 75 | return matched; 76 | } while (*regex == *text++ || // keep running if next symbol matches 77 | (*regex == '.' && escaped == NO && text != textEnd && *text != 0)); 78 | 79 | // running out of text 80 | break; // failed 81 | 82 | case '?': // symbol appears exactly zero or one time 83 | // assume symbol doesn't appear 84 | { 85 | const char* matched = matchhere(text, textEnd, regex + 2, NO); 86 | if (matched != NULL) 87 | return matched; 88 | } 89 | 90 | // continue matching at next position only if symbol appears 91 | if (*regex == *text || (*regex == '.' && escaped == NO && *text != 0)) 92 | return matchhere(text + 1, textEnd, regex + 2, NO); 93 | 94 | break; // failed 95 | 96 | case 0: // *regex will be the last symbol 97 | // dollar matches only end of text 98 | if (*regex == '$' && escaped == NO) 99 | { 100 | if (text == textEnd || *text == 0) 101 | return text; 102 | break; // failed 103 | } 104 | 105 | // nothing left for further matching 106 | if (text == textEnd || *text == 0) 107 | break; // failed 108 | 109 | // does last literal match ? 110 | return (*regex == *text || (*regex == '.' && escaped == NO)) ? text + 1 : NULL; 111 | 112 | default: // no meta-symbol, just plain character 113 | // nothing left for further matching 114 | if (text == textEnd || *text == 0) 115 | break; // failed 116 | 117 | // $ must be followed by 0 (previous case) 118 | if (*regex == '$' && escaped == NO) 119 | break; // failed 120 | 121 | // same character (or any character) ? 122 | if (*regex == *text || (*regex == '.' && escaped == NO)) 123 | return matchhere(text + 1, textEnd, regex + 1, NO); 124 | 125 | break; // failed 126 | } 127 | 128 | // failed 129 | return NULL; 130 | } 131 | 132 | 133 | /// points to first regex match or NULL if not found, text is treated as one line and ^ and $ refer to its start/end 134 | /** @param text - text to be matched 135 | @param regex - simplified regular expression as explained in the header file 136 | @return if successful points to the next matching literal, else NULL if no match 137 | **/ 138 | const char* match(const char* text, const char* regex) 139 | { 140 | // detect invalid input 141 | if (!text || !regex) 142 | return NULL; 143 | 144 | // match only start 145 | if (*regex == '^') 146 | // matchhere looks out for zero byte if textEnd parameter is set to NULL 147 | return matchhere(text, NULL, regex + 1, NO) ? text : NULL; 148 | 149 | // try to start match at every text position 150 | do 151 | { 152 | // match found ? 153 | if (matchhere(text, NULL, regex, NO)) 154 | return text; 155 | } while (*text++ != 0); 156 | 157 | // failed to match 158 | return NULL; 159 | } 160 | 161 | 162 | /// points to first regex match or NULL if not found, text is split into lines (\n) and ^ and $ refer to each line's start/end 163 | /** @param text - text to be matched 164 | @param regex - simplified regular expression as explained in the header file 165 | @return if successful points to the next matching literal, else NULL if no match 166 | **/ 167 | const char* matchlines(const char* text, const char* regex) 168 | { 169 | // detect invalid input 170 | if (!text || !regex) 171 | return NULL; 172 | 173 | // until the end of the world ... 174 | do 175 | { 176 | // use simple matching for end-of-text 177 | if (*text == 0) 178 | return match(text, regex); 179 | 180 | // find extents of current line 181 | const char* right = text; 182 | #ifdef PROCESS_CRLF 183 | while (*right != 0 && *right != '\n' && *right != '\r') 184 | right++; 185 | #else 186 | while (*right != 0 && *right != '\n') 187 | right++; 188 | #endif 189 | 190 | // ^ refers to initial text pointer position in the first line, 191 | // thereafter to each start of a new line 192 | // => if initial text pointer refers to the middle of a line, a false ^ match may be produced 193 | 194 | // match only start 195 | if (*regex == '^') 196 | { 197 | if (matchhere(text, right, regex + 1, NO)) 198 | return text; 199 | } 200 | else 201 | { 202 | // try to start match at every text position 203 | while (text <= right) 204 | { 205 | // match found ? 206 | if (matchhere(text, right, regex, NO)) 207 | return text; 208 | 209 | // no match yet, continue 210 | text++; 211 | } 212 | } 213 | 214 | // skip forward to next line 215 | text = right; 216 | 217 | // skip new-line 218 | #ifdef PROCESS_CRLF 219 | while (*text == '\r') 220 | text++; 221 | #endif 222 | if (*text == '\n') 223 | text++; 224 | } while (*text++ != 0); 225 | 226 | // failed to match 227 | return NULL; 228 | } 229 | -------------------------------------------------------------------------------- /match.h: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // match.h 3 | // Copyright (c) 2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #pragma once 8 | 9 | // allowed metasymbols: 10 | // ^ -> text must start with pattern (only allowed as first symbol in regular expression) 11 | // $ -> text must end with pattern (only allowed as last symbol in regular expression) 12 | // . -> any literal 13 | // x? -> literal x occurs zero or one time 14 | // x* -> literal x is repeated arbitrarily 15 | // x+ -> literal x is repeated at least once 16 | 17 | /// points to first regex match or NULL if not found, text is treated as one line and ^ and $ refer to its start/end 18 | const char* match (const char* text, const char* regex); 19 | 20 | /// points to first regex match or NULL if not found, text is split into lines (\n) and ^ and $ refer to each line's start/end 21 | const char* matchlines (const char* text, const char* regex); 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Tiny Regular Expression Matcher 2 | 3 | Chapter 1 of the excellent book "Beautiful Code" discusses Rob Pike's very short and elegant regex parser code. 4 | It doesn't come with all the bells and whistles of a full Perl regular expression matcher but is quite sufficient for many purposes. 5 | 6 | I extended Rob's C code in such a way that it can handle the following regex meta-symbols: 7 | - `.` - match any character 8 | - `*` - the previous character is arbitrarily repeated 9 | - `+` - the previous character occurs at least once 10 | - `?` - the previous character occurs once or not at all 11 | - `^` - match regular expression only to the beginning of the text 12 | - `$` - match regular expression only to the end of the text 13 | - `\` - treat next character as a literal, even if it is a regex symbol 14 | 15 | My library consists of just one `.h` and one `.c` file - that's why it's called "tiny". 16 | 17 | See my website https://create.stephan-brumme.com/tiny-regular-expression-matcher/ for a live demo, code examples and a simple test suite. 18 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // test.c 3 | // Copyright (c) 2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #include "match.h" 8 | 9 | #include 10 | 11 | 12 | /// count matches 13 | int count(const char* text, const char* regex) 14 | { 15 | int result = 0; 16 | while (text = match(text, regex)) 17 | { 18 | result++; 19 | 20 | if (*text == 0) 21 | break; 22 | text++; 23 | } 24 | return result; 25 | } 26 | 27 | 28 | /// count matching lines 29 | int lines(const char* text, const char* regex) 30 | { 31 | int result = 0; 32 | while (text = matchlines(text, regex)) 33 | { 34 | result++; 35 | 36 | if (*text == 0) 37 | break; 38 | 39 | do 40 | text++; 41 | while (*text != 0 && *text != '\n'); 42 | } 43 | 44 | return result; 45 | } 46 | 47 | 48 | int numErrors = 0; 49 | void error(const char* msg) 50 | { 51 | printf("failed case %s\n", msg); 52 | numErrors++; 53 | } 54 | 55 | 56 | // -------------------------------- 57 | // original code by Rob Pike which my lib is based on 58 | int pike_matchhere(char *regexp, char *text); 59 | int pike_matchstar(int c, char *regexp, char *text); 60 | // in addition, returns -1 if these unsupported symbols are found in regex: +, ?, \ 61 | /* pike_match: search for regexp anywhere in text */ 62 | int pike_match(char *regexp, char *text) 63 | { 64 | /* my code: reject unsupported symbols in regex */ 65 | char* scan = regexp; 66 | while (*scan != '\0') 67 | { 68 | if (*scan == '+' || *scan == '?' || *scan == '\\') 69 | return 0; 70 | scan++; 71 | } 72 | /* end of my code */ 73 | 74 | if (regexp[0] == '^') 75 | return pike_matchhere(regexp+1, text); 76 | do { /* must look even if string is empty */ 77 | if (pike_matchhere(regexp, text)) 78 | return 1; 79 | } while (*text++ != '\0'); 80 | return 0; 81 | } 82 | 83 | /* pike_matchhere: search for regexp at beginning of text */ 84 | int pike_matchhere(char *regexp, char *text) 85 | { 86 | if (regexp[0] == '\0') 87 | return 1; 88 | if (regexp[1] == '*') 89 | return pike_matchstar(regexp[0], regexp+2, text); 90 | if (regexp[0] == '$' && regexp[1] == '\0') 91 | return *text == '\0'; 92 | if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text)) 93 | return pike_matchhere(regexp+1, text+1); 94 | return 0; 95 | } 96 | 97 | /* pike_matchstar: search for c*regexp at beginning of text */ 98 | int pike_matchstar(int c, char *regexp, char *text) 99 | { 100 | do { /* a * matches zero or more instances */ 101 | if (pike_matchhere(regexp, text)) 102 | return 1; 103 | } while (*text != '\0' && (*text++ == c || c == '.')); 104 | return 0; 105 | } 106 | 107 | 108 | int main(int argc, char* argv[]) 109 | { 110 | //#define match(text, regex) pike_match(regex, text) 111 | if (!match("", "")) error("Empty1"); // RobPike: ok 112 | if (!match("abc", "")) error("Empty2"); // RobPike: ok 113 | if (!match("", "a?")) error("Empty3"); // RobPike: unsupported, failed 114 | if (!match("", ".?")) error("Empty4"); // RobPike: unsupported, failed 115 | if (!match("", "a*")) error("Empty5"); // RobPike: ok 116 | if (!match("", ".*")) error("Empty6"); // RobPike: ok 117 | if (!match("", "^")) error("Empty7"); // RobPike: ok 118 | if (!match("", "$")) error("Empty8"); // RobPike: ok 119 | if (!match("", "^$")) error("Empty9"); // RobPike: ok 120 | if (!match("", "^.?")) error("Empty10"); // RobPike: unsupported, failed 121 | if (!match("", "^.*")) error("Empty11"); // RobPike: ok 122 | if (!match("", ".?$")) error("Empty12"); // RobPike: unsupported, failed 123 | if (!match("", ".*$")) error("Empty13"); // RobPike: ok 124 | if (!match("", "^.?$")) error("Empty14"); // RobPike: unsupported, failed 125 | if (!match("", "^.*$")) error("Empty15"); // RobPike: ok 126 | if ( match("", ".")) error("Empty16"); // RobPike: ok 127 | if ( match("", "^.")) error("Empty17"); // RobPike: ok 128 | if ( match("", ".$")) error("Empty18"); // RobPike: ok 129 | if ( match("", "^.$")) error("Empty19"); // RobPike: ok 130 | 131 | if (!match("abc", "abc")) error("Exact1"); // RobPike: ok 132 | if (!match("abc", "ab")) error("Exact2"); // RobPike: ok 133 | if (!match("abc", "bc")) error("Exact3"); // RobPike: ok 134 | if (!match("abc", "^a")) error("Exact4"); // RobPike: ok 135 | if (!match("abc", "^ab")) error("Exact5"); // RobPike: ok 136 | if (!match("abc", "^abc")) error("Exact6"); // RobPike: ok 137 | if (!match("abc", "^abc$")) error("Exact7"); // RobPike: ok 138 | if (!match("abc", "abc$")) error("Exact8"); // RobPike: ok 139 | if (!match("abc", "bc$")) error("Exact9"); // RobPike: ok 140 | if (!match("abc", "c$")) error("Exact10"); // RobPike: ok 141 | if ( match("abc", "Abc")) error("Exact11"); // RobPike: ok 142 | if (!match("aabc", "abc")) error("Exact12"); // RobPike: ok 143 | if (!match("aabcc", "abc")) error("Exact13"); // RobPike: ok 144 | if (!match("abcc", "abc")) error("Exact14"); // RobPike: ok 145 | if ( match("aabc", "^abc")) error("Exact15"); // RobPike: ok 146 | if (!match("aabc", "abc$")) error("Exact16"); // RobPike: ok 147 | if ( match("abcc", "abc$")) error("Exact17"); // RobPike: ok 148 | if (!match("abcc", "^abc")) error("Exact18"); // RobPike: ok 149 | 150 | if (!match("a", "a*")) error("Star1"); // RobPike: ok 151 | if (!match("aa", "a*")) error("Star2"); // RobPike: ok 152 | if (!match("b", "a*")) error("Star3"); // RobPike: ok 153 | if (!match("ab", "a*")) error("Star4"); // RobPike: ok 154 | if (!match("aab", "a*")) error("Star5"); // RobPike: ok 155 | if (!match("a", "^a*")) error("Star6"); // RobPike: ok 156 | if (!match("aa", "^a*")) error("Star7"); // RobPike: ok 157 | if (!match("b", "^a*")) error("Star8"); // RobPike: ok 158 | if (!match("ab", "^a*")) error("Star9"); // RobPike: ok 159 | if (!match("aab", "^a*")) error("Star10"); // RobPike: ok 160 | if (!match("a", "a*$")) error("Star11"); // RobPike: ok 161 | if (!match("aa", "a*$")) error("Star12"); // RobPike: ok 162 | if (!match("b", "a*$")) error("Star13"); // RobPike: ok 163 | if (!match("ba", "a*$")) error("Star14"); // RobPike: ok 164 | if (!match("baa", "a*$")) error("Star15"); // RobPike: ok 165 | if (!match("a", "^a*$")) error("Star16"); // RobPike: ok 166 | if (!match("aa", "^a*$")) error("Star17"); // RobPike: ok 167 | if ( match("b", "^a*$")) error("Star18"); // RobPike: ok 168 | if (!match("a", "a*a")) error("Star19"); // RobPike: ok 169 | if (!match("aa", "a*a")) error("Star20"); // RobPike: ok 170 | 171 | if (!match("a", "a+")) error("Plus1"); // RobPike: unsupported, failed 172 | if (!match("aa", "a+")) error("Plus2"); // RobPike: unsupported, failed 173 | if ( match("b", "a+")) error("Plus3"); // RobPike: unsupported, ok 174 | if (!match("ab", "a+")) error("Plus4"); // RobPike: unsupported, failed 175 | if (!match("aab", "a+")) error("Plus5"); // RobPike: unsupported, failed 176 | if (!match("a", "^a+")) error("Plus6"); // RobPike: unsupported, failed 177 | if (!match("aa", "^a+")) error("Plus7"); // RobPike: unsupported, failed 178 | if ( match("b", "^a+")) error("Plus8"); // RobPike: unsupported, ok 179 | if (!match("ab", "^a+")) error("Plus9"); // RobPike: unsupported, failed 180 | if (!match("aab", "^a+")) error("Plus10"); // RobPike: unsupported, failed 181 | if (!match("a", "a+$")) error("Plus11"); // RobPike: unsupported, failed 182 | if (!match("aa", "a+$")) error("Plus12"); // RobPike: unsupported, failed 183 | if ( match("b", "a+$")) error("Plus13"); // RobPike: unsupported, ok 184 | if (!match("ba", "a+$")) error("Plus14"); // RobPike: unsupported, failed 185 | if (!match("baa", "a+$")) error("Plus15"); // RobPike: unsupported, failed 186 | if (!match("a", "^a+$")) error("Plus16"); // RobPike: unsupported, failed 187 | if (!match("aa", "^a+$")) error("Plus17"); // RobPike: unsupported, failed 188 | if ( match("b", "^a+$")) error("Plus18"); // RobPike: unsupported, ok 189 | if ( match("a", "a+a")) error("Plus19"); // RobPike: unsupported, ok 190 | if (!match("aa", "a+a")) error("Plus20"); // RobPike: unsupported, failed 191 | 192 | if (!match("a", ".")) error("Dot1"); // RobPike: unsupported, ok 193 | if (!match(".", ".")) error("Dot2"); // RobPike: unsupported, ok 194 | if (!match("ab", ".")) error("Dot3"); // RobPike: unsupported, ok 195 | if (!match("a", ".?")) error("Dot4"); // RobPike: unsupported, failed 196 | if (!match("ab", ".?")) error("Dot5"); // RobPike: unsupported, failed 197 | if (!match("a", "^.")) error("Dot6"); // RobPike: unsupported, ok 198 | if (!match("ab", "^.")) error("Dot7"); // RobPike: unsupported, ok 199 | if (!match("a", ".$")) error("Dot8"); // RobPike: unsupported, ok 200 | if (!match("ab", ".$")) error("Dot9"); // RobPike: unsupported, ok 201 | if (!match("a", "^.$")) error("Dot10"); // RobPike: unsupported, ok 202 | if ( match("ab", "^.$")) error("Dot11"); // RobPike: unsupported, ok 203 | 204 | if (!match(".", "\\.")) error("Escape1"); // RobPike: unsupported, failed 205 | if ( match("a", "\\.")) error("Escape2"); // RobPike: unsupported, ok 206 | if (!match("a", "\\.?")) error("Escape3"); // RobPike: unsupported, failed 207 | if (!match("a", "\\.*")) error("Escape4"); // RobPike: unsupported, failed 208 | if ( match("a", "\\.+")) error("Escape5"); // RobPike: unsupported, ok 209 | if (!match("*", "\\*")) error("Escape6"); // RobPike: unsupported, failed 210 | if ( match("a", "\\*")) error("Escape7"); // RobPike: unsupported, ok 211 | if (!match("a", "\\*?")) error("Escape8"); // RobPike: unsupported, failed 212 | if (!match("a", "\\**")) error("Escape9"); // RobPike: unsupported, failed 213 | if ( match("a", "\\*+")) error("Escape10"); // RobPike: unsupported, ok 214 | if (!match("+", "\\+")) error("Escape11"); // RobPike: unsupported, failed 215 | if ( match("a", "\\+")) error("Escape12"); // RobPike: unsupported, ok 216 | if (!match("a", "\\+?")) error("Escape13"); // RobPike: unsupported, failed 217 | if (!match("a", "\\+*")) error("Escape14"); // RobPike: unsupported, failed 218 | if ( match("a", "\\++")) error("Escape15"); // RobPike: unsupported, ok 219 | if (!match("^", "\\^")) error("Escape16"); // RobPike: unsupported, failed 220 | if ( match("a", "\\^")) error("Escape17"); // RobPike: unsupported, ok 221 | if (!match("a", "\\^?")) error("Escape18"); // RobPike: unsupported, failed 222 | if (!match("a", "\\^*")) error("Escape19"); // RobPike: unsupported, failed 223 | if ( match("a", "\\^+")) error("Escape20"); // RobPike: unsupported, ok 224 | if (!match("$", "\\$")) error("Escape21"); // RobPike: unsupported, failed 225 | if ( match("a", "\\$")) error("Escape22"); // RobPike: unsupported, ok 226 | if (!match("a", "\\$?")) error("Escape23"); // RobPike: unsupported, failed 227 | if (!match("a", "\\$*")) error("Escape24"); // RobPike: unsupported, failed 228 | if ( match("a", "\\$+")) error("Escape25"); // RobPike: unsupported, ok 229 | if (!match("?", "\\?")) error("Escape26"); // RobPike: unsupported, failed 230 | if ( match("a", "\\?")) error("Escape27"); // RobPike: unsupported, ok 231 | if (!match("a", "\\??")) error("Escape28"); // RobPike: unsupported, failed 232 | if (!match("a", "\\?*")) error("Escape29"); // RobPike: unsupported, failed 233 | if ( match("a", "\\?+")) error("Escape30"); // RobPike: unsupported, ok 234 | if ( match("\\", "\\")) error("Escape31"); // RobPike: unsupported, ok 235 | if (!match("\\", "\\\\")) error("Escape32"); // RobPike: unsupported, failed 236 | if (!match("a\n", "\n")) error("Escape33"); // RobPike: ok 237 | 238 | if ( match("^", "^^")) error("Begin1"); // RobPike: failed 239 | if ( match("a", "a^")) error("Begin2"); // RobPike: misinterpreted, ok 240 | if (!match("^", "\\^")) error("Begin3"); // RobPike: unsupported, failed 241 | if (!match("^", "^\\^")) error("Begin4"); // RobPike: unsupported, failed 242 | if (!match("^a", "^\\^")) error("Begin5"); // RobPike: unsupported, failed 243 | if ( match("^", "\\^^")) error("Begin6"); // RobPike: unsupported, ok 244 | 245 | if ( match("$", "$$")) error("End1"); // RobPike: failed 246 | if ( match("a", "$a")) error("End2"); // RobPike: misinterpreted, ok 247 | if (!match("$", "\\$")) error("End3"); // RobPike: unsupported, failed 248 | if (!match("$", "\\$$")) error("End4"); // RobPike: unsupported, failed 249 | if (!match("a$", "\\$$")) error("End5"); // RobPike: unsupported, failed 250 | if ( match("$", "$\\$")) error("End6"); // RobPike: unsupported, ok 251 | 252 | // --------------------- 253 | // note: count() cannot be used for ^ matching (but $ is working fine) 254 | 255 | if (count("", "a" ) != 0) error("CountExact1"); 256 | if (count("a", "a" ) != 1) error("CountExact2"); 257 | if (count("aa", "a" ) != 2) error("CountExact3"); 258 | if (count("aaa", "a" ) != 3) error("CountExact4"); 259 | 260 | if (count("aaa", "a*") != 4) error("CountStar1"); 261 | 262 | if (count("aaa", "a+") != 3) error("CountPlus1"); 263 | 264 | if (count("aaa", "." ) != 3) error("CountDot1"); 265 | if (count("aba", "." ) != 3) error("CountDot2"); 266 | if (count("aba", ".." ) != 2) error("CountDot3"); 267 | if (count("aba", "..." ) != 1) error("CountDot4"); 268 | if (count("aba", "...." ) != 0) error("CountDot5"); 269 | if (count("aba", "....?" ) != 1) error("CountDot6"); 270 | if (count("aba", "..?.." ) != 1) error("CountDot7"); 271 | if (count("aba", "..?..?") != 2) error("CountDot8"); 272 | if (count("", "." ) != 0) error("CountDot9"); 273 | if (count("", ".?" ) != 1) error("CountDot10"); 274 | 275 | if (count("aaa", "a$") != 1) error("CountEnd1"); 276 | if (count("aaa", "$" ) != 1) error("CountEnd2"); 277 | 278 | // --------------------- 279 | if (lines("", "" ) != 1) error("LinesExact1"); 280 | if (lines("\n", "" ) != 2) error("LinesExact2"); 281 | 282 | return numErrors; 283 | } 284 | --------------------------------------------------------------------------------