re.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. /*
  2. *
  3. * Mini regex-module inspired by Rob Pike's regex code described in:
  4. *
  5. * http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html
  6. *
  7. *
  8. *
  9. * Supports:
  10. * ---------
  11. * '.' Dot, matches any character
  12. * '^' Start anchor, matches beginning of string
  13. * '$' End anchor, matches end of string
  14. * '*' Asterisk, match zero or more (greedy)
  15. * '+' Plus, match one or more (greedy)
  16. * '?' Question, match zero or one (non-greedy)
  17. * '[abc]' Character class, match if one of {'a', 'b', 'c'}
  18. * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken!
  19. * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z }
  20. * '\s' Whitespace, \t \f \r \n \v and spaces
  21. * '\S' Non-whitespace
  22. * '\w' Alphanumeric, [a-zA-Z0-9_]
  23. * '\W' Non-alphanumeric
  24. * '\d' Digits, [0-9]
  25. * '\D' Non-digits
  26. *
  27. *
  28. */
  29. #include "re.h"
  30. #include <stdio.h>
  31. /* Definitions: */
  32. #define MAX_REGEXP_OBJECTS 30 /* Max number of regex symbols in expression. */
  33. #define MAX_CHAR_CLASS_LEN 40 /* Max length of character-class buffer in. */
  34. enum { UNUSED, DOT, BEGIN, END, QUESTIONMARK, STAR, PLUS, CHAR, CHAR_CLASS, INV_CHAR_CLASS, DIGIT, NOT_DIGIT, ALPHA, NOT_ALPHA, WHITESPACE, NOT_WHITESPACE, BRANCH };
  35. typedef struct regex_t
  36. {
  37. unsigned char type; /* CHAR, STAR, etc. */
  38. union
  39. {
  40. unsigned char ch; /* the character itself */
  41. unsigned char* ccl; /* OR a pointer to characters in class */
  42. };
  43. } regex_t;
  44. /* Private function declarations: */
  45. static int matchpattern(regex_t* pattern, const char* text);
  46. static int matchcharclass(char c, const char* str);
  47. static int matchstar(regex_t p, regex_t* pattern, const char* text);
  48. static int matchplus(regex_t p, regex_t* pattern, const char* text);
  49. static int matchone(regex_t p, char c);
  50. static int matchdigit(char c);
  51. static int matchalpha(char c);
  52. static int matchwhitespace(char c);
  53. static int matchmetachar(char c, const char* str);
  54. static int matchrange(char c, const char* str);
  55. static int ismetachar(char c);
  56. /* Public functions: */
  57. int re_match(const char* pattern, const char* text)
  58. {
  59. return re_matchp(re_compile(pattern), text);
  60. }
  61. int re_matchp(re_t pattern, const char* text)
  62. {
  63. int idx = -1;
  64. if (pattern[0].type == BEGIN)
  65. {
  66. return ((matchpattern(&pattern[1], text)) ? 0 : -1);
  67. }
  68. else
  69. {
  70. do
  71. {
  72. idx += 1;
  73. if (matchpattern(pattern, text))
  74. {
  75. return idx;
  76. }
  77. }
  78. while (*text++ != '\0');
  79. return -1;
  80. }
  81. }
  82. re_t re_compile(const char* pattern)
  83. {
  84. /* The sizes of the two static arrays below substantiates the static RAM usage of this module.
  85. MAX_REGEXP_OBJECTS is the max number of symbols in the expression.
  86. MAX_CHAR_CLASS_LEN determines the size of buffer for chars in all char-classes in the expression. */
  87. static regex_t re_compiled[MAX_REGEXP_OBJECTS];
  88. static unsigned char ccl_buf[MAX_CHAR_CLASS_LEN];
  89. int ccl_bufidx = 1;
  90. char c; /* current char in pattern */
  91. int i = 0; /* index into pattern */
  92. int j = 0; /* index into re_compiled */
  93. while (pattern[i] != '\0')
  94. {
  95. c = pattern[i];
  96. switch (c)
  97. {
  98. /* Meta-characters: */
  99. case '^': { re_compiled[j].type = BEGIN; } break;
  100. case '$': { re_compiled[j].type = END; } break;
  101. case '.': { re_compiled[j].type = DOT; } break;
  102. case '*': { re_compiled[j].type = STAR; } break;
  103. case '+': { re_compiled[j].type = PLUS; } break;
  104. case '?': { re_compiled[j].type = QUESTIONMARK; } break;
  105. case '|': { re_compiled[j].type = BRANCH; } break;
  106. /* Escaped character-classes (\s \w ...): */
  107. case '\\':
  108. {
  109. if (pattern[i+1] != '\0')
  110. {
  111. /* Skip the escape-char '\\' */
  112. i += 1;
  113. /* ... and check the next */
  114. switch (pattern[i])
  115. {
  116. /* Meta-character: */
  117. case 'd': { re_compiled[j].type = DIGIT; } break;
  118. case 'D': { re_compiled[j].type = NOT_DIGIT; } break;
  119. case 'w': { re_compiled[j].type = ALPHA; } break;
  120. case 'W': { re_compiled[j].type = NOT_ALPHA; } break;
  121. case 's': { re_compiled[j].type = WHITESPACE; } break;
  122. case 'S': { re_compiled[j].type = NOT_WHITESPACE; } break;
  123. /* Escaped character, e.g. '.' or '$' */
  124. default:
  125. {
  126. re_compiled[j].type = CHAR;
  127. re_compiled[j].ch = pattern[i];
  128. } break;
  129. }
  130. }
  131. /* '\\' as last char in pattern -> invalid regular expression. */
  132. /*
  133. else
  134. {
  135. re_compiled[j].type = CHAR;
  136. re_compiled[j].ch = pattern[i];
  137. }
  138. */
  139. } break;
  140. /* Character class: */
  141. case '[':
  142. {
  143. /* Remember where the char-buffer starts. */
  144. int buf_begin = ccl_bufidx;
  145. /* Look-ahead to determine if negated */
  146. if (pattern[i+1] == '^')
  147. {
  148. re_compiled[j].type = INV_CHAR_CLASS;
  149. i += 1; /* Increment i to avoid including '^' in the char-buffer */
  150. }
  151. else
  152. {
  153. re_compiled[j].type = CHAR_CLASS;
  154. }
  155. /* Copy characters inside [..] to buffer */
  156. while ( (pattern[++i] != ']')
  157. && (pattern[i] != '\0')) /* Missing ] */
  158. {
  159. ccl_buf[ccl_bufidx++] = pattern[i];
  160. }
  161. /* Null-terminate string end */
  162. ccl_buf[ccl_bufidx++] = 0;
  163. ccl_buf[ccl_bufidx] = 0;
  164. re_compiled[j].ccl = &ccl_buf[buf_begin];
  165. } break;
  166. /* Other characters: */
  167. default:
  168. {
  169. re_compiled[j].type = CHAR;
  170. re_compiled[j].ch = c;
  171. } break;
  172. }
  173. i += 1;
  174. j += 1;
  175. }
  176. /* 'UNUSED' is a sentinel used to indicate end-of-pattern */
  177. re_compiled[j].type = UNUSED;
  178. return (re_t) re_compiled;
  179. }
  180. void re_print(regex_t* pattern)
  181. {
  182. const char* types[] = { "UNUSED", "DOT", "BEGIN", "END", "QUESTIONMARK", "STAR", "PLUS", "CHAR", "CHAR_CLASS", "INV_CHAR_CLASS", "DIGIT", "NOT_DIGIT", "ALPHA", "NOT_ALPHA", "WHITESPACE", "NOT_WHITESPACE", "BRANCH" };
  183. int i;
  184. for (i = 0; i < MAX_REGEXP_OBJECTS; ++i)
  185. {
  186. if (pattern[i].type == UNUSED)
  187. {
  188. break;
  189. }
  190. printf("type: %s", types[pattern[i].type]);
  191. if (pattern[i].type == CHAR_CLASS || pattern[i].type == INV_CHAR_CLASS)
  192. {
  193. printf(" [");
  194. int j;
  195. char c;
  196. for (j = 0; j < MAX_CHAR_CLASS_LEN; ++j)
  197. {
  198. c = pattern[i].ccl[j];
  199. if ((c == '\0') || (c == ']'))
  200. {
  201. break;
  202. }
  203. printf("%c", c);
  204. }
  205. printf("]");
  206. }
  207. else if (pattern[i].type == CHAR)
  208. {
  209. printf(" '%c'", pattern[i].ch);
  210. }
  211. printf("\n");
  212. }
  213. }
  214. /* Private functions: */
  215. static int matchdigit(char c)
  216. {
  217. return ((c >= '0') && (c <= '9'));
  218. }
  219. static int matchalpha(char c)
  220. {
  221. return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
  222. }
  223. static int matchwhitespace(char c)
  224. {
  225. return ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') || (c == '\f') || (c == '\v'));
  226. }
  227. static int matchalphanum(char c)
  228. {
  229. return ((c == '_') || matchalpha(c) || matchdigit(c));
  230. }
  231. static int matchrange(char c, const char* str)
  232. {
  233. return ((c != '-') && (str[0] != '-') && (str[1] == '-') && (str[2] != '\0') && ((c >= str[0]) && (c <= str[2])));
  234. }
  235. static int ismetachar(char c)
  236. {
  237. return ((c == 's') || (c == 'S') == (c == 'w') || (c == 'W') || (c == 'd') || (c == 'D'));
  238. }
  239. static int matchmetachar(char c, const char* str)
  240. {
  241. switch (str[0])
  242. {
  243. case 'd': return matchdigit(c);
  244. case 'D': return !matchdigit(c);
  245. case 'w': return matchalphanum(c);
  246. case 'W': return !matchalphanum(c);
  247. case 's': return matchwhitespace(c);
  248. case 'S': return !matchwhitespace(c);
  249. default: return (c == str[0]);
  250. }
  251. }
  252. static int matchcharclass(char c, const char* str)
  253. {
  254. do
  255. {
  256. if (matchrange(c, str))
  257. {
  258. return 1;
  259. }
  260. else if (str[0] == '\\')
  261. {
  262. /* Escape-char: increment str-ptr and match on next char */
  263. str += 1;
  264. if (matchmetachar(c, str))
  265. {
  266. return 1;
  267. }
  268. else if ((c == str[0]) && !ismetachar(c))
  269. {
  270. return 1;
  271. }
  272. }
  273. else if (c == str[0])
  274. {
  275. if (c == '-')
  276. {
  277. return ((str[-1] == '\0') || (str[1] == '\0'));
  278. }
  279. else
  280. {
  281. return 1;
  282. }
  283. }
  284. }
  285. while (*str++ != '\0');
  286. return 0;
  287. }
  288. static int matchone(regex_t p, char c)
  289. {
  290. switch (p.type)
  291. {
  292. case DOT: return 1;
  293. case CHAR_CLASS: return matchcharclass(c, (const char*)p.ccl);
  294. case INV_CHAR_CLASS: return !matchcharclass(c, (const char*)p.ccl);
  295. case DIGIT: return matchdigit(c);
  296. case NOT_DIGIT: return !matchdigit(c);
  297. case ALPHA: return matchalphanum(c);
  298. case NOT_ALPHA: return !matchalphanum(c);
  299. case WHITESPACE: return matchwhitespace(c);
  300. case NOT_WHITESPACE: return !matchwhitespace(c);
  301. default: return (p.ch == c);
  302. }
  303. }
  304. static int matchstar(regex_t p, regex_t* pattern, const char* text)
  305. {
  306. do
  307. {
  308. if (matchpattern(pattern, text))
  309. return 1;
  310. }
  311. while ((text[0] != '\0') && matchone(p, *text++));
  312. return 0;
  313. }
  314. static int matchplus(regex_t p, regex_t* pattern, const char* text)
  315. {
  316. while ((text[0] != '\0') && matchone(p, *text++))
  317. {
  318. if (matchpattern(pattern, text))
  319. return 1;
  320. }
  321. return 0;
  322. }
  323. #if 0
  324. /* Recursive matching */
  325. static int matchpattern(regex_t* pattern, const char* text)
  326. {
  327. if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK))
  328. {
  329. return 1;
  330. }
  331. else if (pattern[1].type == STAR)
  332. {
  333. return matchstar(pattern[0], &pattern[2], text);
  334. }
  335. else if (pattern[1].type == PLUS)
  336. {
  337. return matchplus(pattern[0], &pattern[2], text);
  338. }
  339. else if ((pattern[0].type == END) && pattern[1].type == UNUSED)
  340. {
  341. return text[0] == '\0';
  342. }
  343. else if ((text[0] != '\0') && matchone(pattern[0], text[0]))
  344. {
  345. return matchpattern(&pattern[1], text+1);
  346. }
  347. else
  348. {
  349. return 0;
  350. }
  351. }
  352. #else
  353. /* Iterative matching */
  354. static int matchpattern(regex_t* pattern, const char* text)
  355. {
  356. do
  357. {
  358. if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK))
  359. {
  360. return 1;
  361. }
  362. else if (pattern[1].type == STAR)
  363. {
  364. return matchstar(pattern[0], &pattern[2], text);
  365. }
  366. else if (pattern[1].type == PLUS)
  367. {
  368. return matchplus(pattern[0], &pattern[2], text);
  369. }
  370. else if ((pattern[0].type == END) && pattern[1].type == UNUSED)
  371. {
  372. return (text[0] == '\0');
  373. }
  374. else if (pattern[1].type == BRANCH)
  375. {
  376. return (matchpattern(pattern, text) || matchpattern(&pattern[2], text));
  377. }
  378. }
  379. while ((text[0] != '\0') && matchone(*pattern++, *text++));
  380. return 0;
  381. }
  382. #endif