usbmodeswitch: Updated to v.1.2.6 from shibby's branch.
[tomato.git] / release / src / router / usbmodeswitch / jim / jim-regexp.c
blob2ccd996cd80b9081e17fe01ce42da3c231a2b09e
1 /*
2 * Implements the regexp and regsub commands for Jim
4 * (c) 2008 Steve Bennett <steveb@workware.net.au>
6 * Uses C library regcomp()/regexec() for the matching.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials
17 * provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
30 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 * The views and conclusions contained in the software and documentation
33 * are those of the authors and should not be interpreted as representing
34 * official policies, either expressed or implied, of the Jim Tcl Project.
36 * Based on code originally from Tcl 6.7:
38 * Copyright 1987-1991 Regents of the University of California
39 * Permission to use, copy, modify, and distribute this
40 * software and its documentation for any purpose and without
41 * fee is hereby granted, provided that the above copyright
42 * notice appear in all copies. The University of California
43 * makes no representations about the suitability of this
44 * software for any purpose. It is provided "as is" without
45 * express or implied warranty.
48 #include <stdlib.h>
49 #include <string.h>
51 #include "jim.h"
52 #include "jimautoconf.h"
53 #include "jimregexp.h"
55 static void FreeRegexpInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
57 regfree(objPtr->internalRep.regexpValue.compre);
58 Jim_Free(objPtr->internalRep.regexpValue.compre);
61 static const Jim_ObjType regexpObjType = {
62 "regexp",
63 FreeRegexpInternalRep,
64 NULL,
65 NULL,
66 JIM_TYPE_NONE
69 static regex_t *SetRegexpFromAny(Jim_Interp *interp, Jim_Obj *objPtr, unsigned flags)
71 regex_t *compre;
72 const char *pattern;
73 int ret;
75 /* Check if the object is already an uptodate variable */
76 if (objPtr->typePtr == &regexpObjType &&
77 objPtr->internalRep.regexpValue.compre && objPtr->internalRep.regexpValue.flags == flags) {
78 /* nothing to do */
79 return objPtr->internalRep.regexpValue.compre;
82 /* Not a regexp or the flags do not match */
84 /* Get the string representation */
85 pattern = Jim_String(objPtr);
86 compre = Jim_Alloc(sizeof(regex_t));
88 if ((ret = regcomp(compre, pattern, REG_EXTENDED | flags)) != 0) {
89 char buf[100];
91 regerror(ret, compre, buf, sizeof(buf));
92 Jim_SetResultFormatted(interp, "couldn't compile regular expression pattern: %s", buf);
93 regfree(compre);
94 Jim_Free(compre);
95 return NULL;
98 Jim_FreeIntRep(interp, objPtr);
100 objPtr->typePtr = &regexpObjType;
101 objPtr->internalRep.regexpValue.flags = flags;
102 objPtr->internalRep.regexpValue.compre = compre;
104 return compre;
107 int Jim_RegexpCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
109 int opt_indices = 0;
110 int opt_all = 0;
111 int opt_inline = 0;
112 regex_t *regex;
113 int match, i, j;
114 int offset = 0;
115 regmatch_t *pmatch = NULL;
116 int source_len;
117 int result = JIM_OK;
118 const char *pattern;
119 const char *source_str;
120 int num_matches = 0;
121 int num_vars;
122 Jim_Obj *resultListObj = NULL;
123 int regcomp_flags = 0;
124 int eflags = 0;
125 int option;
126 enum {
127 OPT_INDICES, OPT_NOCASE, OPT_LINE, OPT_ALL, OPT_INLINE, OPT_START, OPT_END
129 static const char * const options[] = {
130 "-indices", "-nocase", "-line", "-all", "-inline", "-start", "--", NULL
133 if (argc < 3) {
134 wrongNumArgs:
135 Jim_WrongNumArgs(interp, 1, argv,
136 "?switches? exp string ?matchVar? ?subMatchVar subMatchVar ...?");
137 return JIM_ERR;
140 for (i = 1; i < argc; i++) {
141 const char *opt = Jim_String(argv[i]);
143 if (*opt != '-') {
144 break;
146 if (Jim_GetEnum(interp, argv[i], options, &option, "switch", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) {
147 return JIM_ERR;
149 if (option == OPT_END) {
150 i++;
151 break;
153 switch (option) {
154 case OPT_INDICES:
155 opt_indices = 1;
156 break;
158 case OPT_NOCASE:
159 regcomp_flags |= REG_ICASE;
160 break;
162 case OPT_LINE:
163 regcomp_flags |= REG_NEWLINE;
164 break;
166 case OPT_ALL:
167 opt_all = 1;
168 break;
170 case OPT_INLINE:
171 opt_inline = 1;
172 break;
174 case OPT_START:
175 if (++i == argc) {
176 goto wrongNumArgs;
178 if (Jim_GetIndex(interp, argv[i], &offset) != JIM_OK) {
179 return JIM_ERR;
181 break;
184 if (argc - i < 2) {
185 goto wrongNumArgs;
188 regex = SetRegexpFromAny(interp, argv[i], regcomp_flags);
189 if (!regex) {
190 return JIM_ERR;
193 pattern = Jim_String(argv[i]);
194 source_str = Jim_GetString(argv[i + 1], &source_len);
196 num_vars = argc - i - 2;
198 if (opt_inline) {
199 if (num_vars) {
200 Jim_SetResultString(interp, "regexp match variables not allowed when using -inline",
201 -1);
202 result = JIM_ERR;
203 goto done;
205 num_vars = regex->re_nsub + 1;
208 pmatch = Jim_Alloc((num_vars + 1) * sizeof(*pmatch));
210 /* If an offset has been specified, adjust for that now.
211 * If it points past the end of the string, point to the terminating null
213 if (offset) {
214 if (offset < 0) {
215 offset += source_len + 1;
217 if (offset > source_len) {
218 source_str += source_len;
220 else if (offset > 0) {
221 source_str += offset;
223 eflags |= REG_NOTBOL;
226 if (opt_inline) {
227 resultListObj = Jim_NewListObj(interp, NULL, 0);
230 next_match:
231 match = regexec(regex, source_str, num_vars + 1, pmatch, eflags);
232 if (match >= REG_BADPAT) {
233 char buf[100];
235 regerror(match, regex, buf, sizeof(buf));
236 Jim_SetResultFormatted(interp, "error while matching pattern: %s", buf);
237 result = JIM_ERR;
238 goto done;
241 if (match == REG_NOMATCH) {
242 goto done;
245 num_matches++;
247 if (opt_all && !opt_inline) {
248 /* Just count the number of matches, so skip the substitution h */
249 goto try_next_match;
253 * If additional variable names have been specified, return
254 * index information in those variables.
257 j = 0;
258 for (i += 2; opt_inline ? j < num_vars : i < argc; i++, j++) {
259 Jim_Obj *resultObj;
261 if (opt_indices) {
262 resultObj = Jim_NewListObj(interp, NULL, 0);
264 else {
265 resultObj = Jim_NewStringObj(interp, "", 0);
268 if (pmatch[j].rm_so == -1) {
269 if (opt_indices) {
270 Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp, -1));
271 Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp, -1));
274 else {
275 int len = pmatch[j].rm_eo - pmatch[j].rm_so;
277 if (opt_indices) {
278 Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp,
279 offset + pmatch[j].rm_so));
280 Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp,
281 offset + pmatch[j].rm_so + len - 1));
283 else {
284 Jim_AppendString(interp, resultObj, source_str + pmatch[j].rm_so, len);
288 if (opt_inline) {
289 Jim_ListAppendElement(interp, resultListObj, resultObj);
291 else {
292 /* And now set the result variable */
293 result = Jim_SetVariable(interp, argv[i], resultObj);
295 if (result != JIM_OK) {
296 Jim_FreeObj(interp, resultObj);
297 break;
302 try_next_match:
303 if (opt_all && (pattern[0] != '^' || (regcomp_flags & REG_NEWLINE)) && *source_str) {
304 if (pmatch[0].rm_eo) {
305 offset += pmatch[0].rm_eo;
306 source_str += pmatch[0].rm_eo;
308 else {
309 source_str++;
310 offset++;
312 if (*source_str) {
313 eflags = REG_NOTBOL;
314 goto next_match;
318 done:
319 if (result == JIM_OK) {
320 if (opt_inline) {
321 Jim_SetResult(interp, resultListObj);
323 else {
324 Jim_SetResultInt(interp, num_matches);
328 Jim_Free(pmatch);
329 return result;
332 #define MAX_SUB_MATCHES 50
334 int Jim_RegsubCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
336 int regcomp_flags = 0;
337 int regexec_flags = 0;
338 int opt_all = 0;
339 int offset = 0;
340 regex_t *regex;
341 const char *p;
342 int result;
343 regmatch_t pmatch[MAX_SUB_MATCHES + 1];
344 int num_matches = 0;
346 int i, j, n;
347 Jim_Obj *varname;
348 Jim_Obj *resultObj;
349 const char *source_str;
350 int source_len;
351 const char *replace_str;
352 int replace_len;
353 const char *pattern;
354 int option;
355 enum {
356 OPT_NOCASE, OPT_LINE, OPT_ALL, OPT_START, OPT_END
358 static const char * const options[] = {
359 "-nocase", "-line", "-all", "-start", "--", NULL
362 if (argc < 4) {
363 wrongNumArgs:
364 Jim_WrongNumArgs(interp, 1, argv,
365 "?switches? exp string subSpec ?varName?");
366 return JIM_ERR;
369 for (i = 1; i < argc; i++) {
370 const char *opt = Jim_String(argv[i]);
372 if (*opt != '-') {
373 break;
375 if (Jim_GetEnum(interp, argv[i], options, &option, "switch", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) {
376 return JIM_ERR;
378 if (option == OPT_END) {
379 i++;
380 break;
382 switch (option) {
383 case OPT_NOCASE:
384 regcomp_flags |= REG_ICASE;
385 break;
387 case OPT_LINE:
388 regcomp_flags |= REG_NEWLINE;
389 break;
391 case OPT_ALL:
392 opt_all = 1;
393 break;
395 case OPT_START:
396 if (++i == argc) {
397 goto wrongNumArgs;
399 if (Jim_GetIndex(interp, argv[i], &offset) != JIM_OK) {
400 return JIM_ERR;
402 break;
405 if (argc - i != 3 && argc - i != 4) {
406 goto wrongNumArgs;
409 regex = SetRegexpFromAny(interp, argv[i], regcomp_flags);
410 if (!regex) {
411 return JIM_ERR;
413 pattern = Jim_String(argv[i]);
415 source_str = Jim_GetString(argv[i + 1], &source_len);
416 replace_str = Jim_GetString(argv[i + 2], &replace_len);
417 varname = argv[i + 3];
419 /* Create the result string */
420 resultObj = Jim_NewStringObj(interp, "", 0);
422 /* If an offset has been specified, adjust for that now.
423 * If it points past the end of the string, point to the terminating null
425 if (offset) {
426 if (offset < 0) {
427 offset += source_len + 1;
429 if (offset > source_len) {
430 offset = source_len;
432 else if (offset < 0) {
433 offset = 0;
437 /* Copy the part before -start */
438 Jim_AppendString(interp, resultObj, source_str, offset);
441 * The following loop is to handle multiple matches within the
442 * same source string; each iteration handles one match and its
443 * corresponding substitution. If "-all" hasn't been specified
444 * then the loop body only gets executed once.
447 n = source_len - offset;
448 p = source_str + offset;
449 do {
450 int match = regexec(regex, p, MAX_SUB_MATCHES, pmatch, regexec_flags);
452 if (match >= REG_BADPAT) {
453 char buf[100];
455 regerror(match, regex, buf, sizeof(buf));
456 Jim_SetResultFormatted(interp, "error while matching pattern: %s", buf);
457 return JIM_ERR;
459 if (match == REG_NOMATCH) {
460 break;
463 num_matches++;
466 * Copy the portion of the source string before the match to the
467 * result variable.
469 Jim_AppendString(interp, resultObj, p, pmatch[0].rm_so);
472 * Append the subSpec (replace_str) argument to the variable, making appropriate
473 * substitutions. This code is a bit hairy because of the backslash
474 * conventions and because the code saves up ranges of characters in
475 * subSpec to reduce the number of calls to Jim_SetVar.
478 for (j = 0; j < replace_len; j++) {
479 int idx;
480 int c = replace_str[j];
482 if (c == '&') {
483 idx = 0;
485 else if (c == '\\' && j < replace_len) {
486 c = replace_str[++j];
487 if ((c >= '0') && (c <= '9')) {
488 idx = c - '0';
490 else if ((c == '\\') || (c == '&')) {
491 Jim_AppendString(interp, resultObj, replace_str + j, 1);
492 continue;
494 else {
495 Jim_AppendString(interp, resultObj, replace_str + j - 1, 2);
496 continue;
499 else {
500 Jim_AppendString(interp, resultObj, replace_str + j, 1);
501 continue;
503 if ((idx < MAX_SUB_MATCHES) && pmatch[idx].rm_so != -1 && pmatch[idx].rm_eo != -1) {
504 Jim_AppendString(interp, resultObj, p + pmatch[idx].rm_so,
505 pmatch[idx].rm_eo - pmatch[idx].rm_so);
509 p += pmatch[0].rm_eo;
510 n -= pmatch[0].rm_eo;
512 /* If -all is not specified, or there is no source left, we are done */
513 if (!opt_all || n == 0) {
514 break;
517 /* An anchored pattern without -line must be done */
518 if ((regcomp_flags & REG_NEWLINE) == 0 && pattern[0] == '^') {
519 break;
522 /* If the pattern is empty, need to step forwards */
523 if (pattern[0] == '\0' && n) {
524 /* Need to copy the char we are moving over */
525 Jim_AppendString(interp, resultObj, p, 1);
526 p++;
527 n--;
530 regexec_flags |= REG_NOTBOL;
531 } while (n);
534 * Copy the portion of the string after the last match to the
535 * result variable.
537 Jim_AppendString(interp, resultObj, p, -1);
539 /* And now set or return the result variable */
540 if (argc - i == 4) {
541 result = Jim_SetVariable(interp, varname, resultObj);
543 if (result == JIM_OK) {
544 Jim_SetResultInt(interp, num_matches);
546 else {
547 Jim_FreeObj(interp, resultObj);
550 else {
551 Jim_SetResult(interp, resultObj);
552 result = JIM_OK;
555 return result;
558 int Jim_regexpInit(Jim_Interp *interp)
560 if (Jim_PackageProvide(interp, "regexp", "1.0", JIM_ERRMSG))
561 return JIM_ERR;
563 Jim_CreateCommand(interp, "regexp", Jim_RegexpCmd, NULL, NULL);
564 Jim_CreateCommand(interp, "regsub", Jim_RegsubCmd, NULL, NULL);
565 return JIM_OK;