Changeset: 32993207234e for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=32993207234e
Modified Files:
        clients/Tests/MAL-signatures.stable.out
        clients/Tests/MAL-signatures.stable.out.int128
        clients/Tests/exports.stable.out
        monetdb5/ChangeLog
        monetdb5/modules/mal/pcre.c
        monetdb5/modules/mal/pcre.mal
Branch: default
Log Message:

Implemented pcre.replace_first.
Many thanks to Roberto Cornacchia who actually did the implementation work.


diffs (truncated from 672 to 300 lines):

diff --git a/clients/Tests/MAL-signatures.stable.out 
b/clients/Tests/MAL-signatures.stable.out
--- a/clients/Tests/MAL-signatures.stable.out
+++ b/clients/Tests/MAL-signatures.stable.out
@@ -8238,6 +8238,7 @@ Ready.
 [ "batmtime",  "sql_seconds",  "command 
batmtime.sql_seconds(d:bat[:daytime]):bat[:int] ",     
"MTIMEdaytime_extract_sql_seconds_bulk;",       ""      ]
 [ "batmtime",  "year", "command batmtime.year(d:bat[:date]):bat[:int] ",       
"MTIMEdate_extract_year_bulk;", ""      ]
 [ "batpcre",   "replace",      "command batpcre.replace(orig:bat[:str], 
pat:str, repl:str, flag:str):bat[:str] ",      "PCREreplace_bat_wrap;",        
""      ]
+[ "batpcre",   "replace_first",        "command 
batpcre.replace_first(orig:bat[:str], pat:str, repl:str, flag:str):bat[:str] ", 
       "PCREreplacefirst_bat_wrap;",   ""      ]
 [ "batpyapi",  "eval", "pattern batpyapi.eval(fptr:ptr, expr:str, 
arg:any...):any... ",        "PYAPI2PyAPIevalStd;",  "Execute a simple Python 
script value"  ]
 [ "batpyapi",  "eval_aggr",    "pattern batpyapi.eval_aggr(fptr:ptr, expr:str, 
arg:any...):any... ",   "PYAPI2PyAPIevalAggr;", "grouped aggregates through 
Python"     ]
 [ "batpyapi",  "eval_loader",  "pattern batpyapi.eval_loader(fptr:ptr, 
expr:str):any... ",     "PYAPI2PyAPIevalLoader;",       "loader functions 
through Python"       ]
@@ -10704,6 +10705,7 @@ Ready.
 [ "pcre",      "pcre_quote",   "command pcre.pcre_quote(s:str):str ",  
"PCREquote;",   "Return a PCRE pattern string that matches the argument 
exactly."       ]
 [ "pcre",      "prelude",      "command pcre.prelude():void ", "pcre_init;",   
"Initialize pcre"       ]
 [ "pcre",      "replace",      "command pcre.replace(origin:str, pat:str, 
repl:str, flags:str):str ",  "PCREreplace_wrap;",    "Replace _all_ matches of 
\"pattern\" in \"origin_str\" with \"replacement\".\n\t Parameter \"flags\" 
accept these flags: 'i', 'm', 's', and 'x'.\n\t   'e': if present, an empty 
string is considered to be a valid match\n\t   'i': if present, the match 
operates in case-insensitive mode.\n\t\tOtherwise, in case-sensitive mode.\n\t  
 'm': if present, the match operates in multi-line mode.\n\t   's': if present, 
the match operates in \"dot-all\"\n\t   The specifications of the flags can be 
found in \"man pcreapi\"\n\t   The flag letters may be repeated.\n\t   No other 
letters than 'e', 'i', 'm', 's' and 'x' are allowed in \"flags\".\n\t   Returns 
the replaced string, or if no matches found, the original string."   ]
+[ "pcre",      "replace_first",        "command pcre.replace_first(origin:str, 
pat:str, repl:str, flags:str):str ",    "PCREreplace_wrap;",    "Replace _the 
first_ match of \"pattern\" in \"origin_str\" with \"replacement\".\n\t 
Parameter \"flags\" accept these flags: 'i', 'm', 's', and 'x'.\n\t   'e': if 
present, an empty string is considered to be a valid match\n\t   'i': if 
present, the match operates in case-insensitive mode.\n\t\tOtherwise, in 
case-sensitive mode.\n\t   'm': if present, the match operates in multi-line 
mode.\n\t   's': if present, the match operates in \"dot-all\"\n\t   The 
specifications of the flags can be found in \"man pcreapi\"\n\t   The flag 
letters may be repeated.\n\t   No other letters than 'e', 'i', 'm', 's' and 'x' 
are allowed in \"flags\".\n\t   Returns the replaced string, or if no matches 
found, the original string."       ]
 [ "pcre",      "sql2pcre",     "command pcre.sql2pcre(pat:str, esc:str):str ", 
"PCREsql2pcre;",        "Convert a SQL like pattern with the given escape 
character into a PCRE pattern."       ]
 [ "profiler",  "cleanup",      "command profiler.cleanup():void ",     
"CMDcleanupTraces;",    "Remove the temporary tables for profiling"     ]
 [ "profiler",  "closestream",  "command profiler.closestream():void ", 
"CMDcloseProfilerStream;",      "Stop offline proviling"        ]
diff --git a/clients/Tests/MAL-signatures.stable.out.int128 
b/clients/Tests/MAL-signatures.stable.out.int128
--- a/clients/Tests/MAL-signatures.stable.out.int128
+++ b/clients/Tests/MAL-signatures.stable.out.int128
@@ -11806,6 +11806,7 @@ Ready.
 [ "batmtime",  "sql_seconds",  "command 
batmtime.sql_seconds(d:bat[:daytime]):bat[:int] ",     
"MTIMEdaytime_extract_sql_seconds_bulk;",       ""      ]
 [ "batmtime",  "year", "command batmtime.year(d:bat[:date]):bat[:int] ",       
"MTIMEdate_extract_year_bulk;", ""      ]
 [ "batpcre",   "replace",      "command batpcre.replace(orig:bat[:str], 
pat:str, repl:str, flag:str):bat[:str] ",      "PCREreplace_bat_wrap;",        
""      ]
+[ "batpcre",   "replace_first",        "command 
batpcre.replace_first(orig:bat[:str], pat:str, repl:str, flag:str):bat[:str] ", 
       "PCREreplacefirst_bat_wrap;",   ""      ]
 [ "batpyapi",  "eval", "pattern batpyapi.eval(fptr:ptr, expr:str, 
arg:any...):any... ",        "PYAPI2PyAPIevalStd;",  "Execute a simple Python 
script value"  ]
 [ "batpyapi",  "eval_aggr",    "pattern batpyapi.eval_aggr(fptr:ptr, expr:str, 
arg:any...):any... ",   "PYAPI2PyAPIevalAggr;", "grouped aggregates through 
Python"     ]
 [ "batpyapi",  "eval_loader",  "pattern batpyapi.eval_loader(fptr:ptr, 
expr:str):any... ",     "PYAPI2PyAPIevalLoader;",       "loader functions 
through Python"       ]
@@ -14915,6 +14916,7 @@ Ready.
 [ "pcre",      "pcre_quote",   "command pcre.pcre_quote(s:str):str ",  
"PCREquote;",   "Return a PCRE pattern string that matches the argument 
exactly."       ]
 [ "pcre",      "prelude",      "command pcre.prelude():void ", "pcre_init;",   
"Initialize pcre"       ]
 [ "pcre",      "replace",      "command pcre.replace(origin:str, pat:str, 
repl:str, flags:str):str ",  "PCREreplace_wrap;",    "Replace _all_ matches of 
\"pattern\" in \"origin_str\" with \"replacement\".\n\t Parameter \"flags\" 
accept these flags: 'i', 'm', 's', and 'x'.\n\t   'e': if present, an empty 
string is considered to be a valid match\n\t   'i': if present, the match 
operates in case-insensitive mode.\n\t\tOtherwise, in case-sensitive mode.\n\t  
 'm': if present, the match operates in multi-line mode.\n\t   's': if present, 
the match operates in \"dot-all\"\n\t   The specifications of the flags can be 
found in \"man pcreapi\"\n\t   The flag letters may be repeated.\n\t   No other 
letters than 'e', 'i', 'm', 's' and 'x' are allowed in \"flags\".\n\t   Returns 
the replaced string, or if no matches found, the original string."   ]
+[ "pcre",      "replace_first",        "command pcre.replace_first(origin:str, 
pat:str, repl:str, flags:str):str ",    "PCREreplace_wrap;",    "Replace _the 
first_ match of \"pattern\" in \"origin_str\" with \"replacement\".\n\t 
Parameter \"flags\" accept these flags: 'i', 'm', 's', and 'x'.\n\t   'e': if 
present, an empty string is considered to be a valid match\n\t   'i': if 
present, the match operates in case-insensitive mode.\n\t\tOtherwise, in 
case-sensitive mode.\n\t   'm': if present, the match operates in multi-line 
mode.\n\t   's': if present, the match operates in \"dot-all\"\n\t   The 
specifications of the flags can be found in \"man pcreapi\"\n\t   The flag 
letters may be repeated.\n\t   No other letters than 'e', 'i', 'm', 's' and 'x' 
are allowed in \"flags\".\n\t   Returns the replaced string, or if no matches 
found, the original string."       ]
 [ "pcre",      "sql2pcre",     "command pcre.sql2pcre(pat:str, esc:str):str ", 
"PCREsql2pcre;",        "Convert a SQL like pattern with the given escape 
character into a PCRE pattern."       ]
 [ "profiler",  "cleanup",      "command profiler.cleanup():void ",     
"CMDcleanupTraces;",    "Remove the temporary tables for profiling"     ]
 [ "profiler",  "closestream",  "command profiler.closestream():void ", 
"CMDcloseProfilerStream;",      "Stop offline proviling"        ]
diff --git a/clients/Tests/exports.stable.out b/clients/Tests/exports.stable.out
--- a/clients/Tests/exports.stable.out
+++ b/clients/Tests/exports.stable.out
@@ -1614,6 +1614,8 @@ str PCREpatindex(int *ret, const str *pa
 str PCREquote(str *r, const str *v);
 str PCREreplace_bat_wrap(bat *res, const bat *or, const str *pat, const str 
*repl, const str *flags);
 str PCREreplace_wrap(str *res, const str *or, const str *pat, const str *repl, 
const str *flags);
+str PCREreplacefirst_bat_wrap(bat *res, const bat *or, const str *pat, const 
str *repl, const str *flags);
+str PCREreplacefirst_wrap(str *res, const str *or, const str *pat, const str 
*repl, const str *flags);
 str PCREsql2pcre(str *ret, const str *pat, const str *esc);
 str PROFexitClient(Client c);
 str PROFinitClient(Client c);
diff --git a/monetdb5/ChangeLog b/monetdb5/ChangeLog
--- a/monetdb5/ChangeLog
+++ b/monetdb5/ChangeLog
@@ -1,6 +1,10 @@
 # ChangeLog file for MonetDB5
 # This file is updated with Maddlog
 
+* Wed Jan 24 2018 Sjoerd Mullender <[email protected]>
+- Implemented function pcre.replace_first which is like pcre.replace,
+  except it only replaces the first match.
+
 * Wed Aug 23 2017 Martin Kersten <[email protected]>
 - The EXPLAIN command now shows all the MAL type resolutions, because in
   general users may not be aware of their signatures.  It also simplifies
diff --git a/monetdb5/modules/mal/pcre.c b/monetdb5/modules/mal/pcre.c
--- a/monetdb5/modules/mal/pcre.c
+++ b/monetdb5/modules/mal/pcre.c
@@ -1,3 +1,4 @@
+#pragma GCC optimize ("-fno-stack-protector")
 /*
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0.  If a copy of the MPL was not distributed with this
@@ -43,6 +44,8 @@ mal_export str PCREindex(int *ret, const
 mal_export str PCREpatindex(int *ret, const str *pat, const str *val);
 mal_export str PCREreplace_wrap(str *res, const str *or, const str *pat, const 
str *repl, const str *flags);
 mal_export str PCREreplace_bat_wrap(bat *res, const bat *or, const str *pat, 
const str *repl, const str *flags);
+mal_export str PCREreplacefirst_wrap(str *res, const str *or, const str *pat, 
const str *repl, const str *flags);
+mal_export str PCREreplacefirst_bat_wrap(bat *res, const bat *or, const str 
*pat, const str *repl, const str *flags);
 mal_export str PCREsql2pcre(str *ret, const str *pat, const str *esc);
 
 mal_export str PCRElike3(bit *ret, const str *s, const str *pat, const str 
*esc);
@@ -549,22 +552,28 @@ re_likeselect(BAT **bnp, BAT *b, BAT *s,
        throw(MAL, "pcre.likeselect", OPERATION_FAILED);
 }
 
-#define MAX_NR_CAPTURES  1024 /* Maximal number of captured substrings in one 
original string */
+#define MAX_NR_MATCHES   1024 /* Maximal number of matches in one original 
string */
+#define MAX_NR_CAPTURES  10 /* Maximal number of captured substrings per match 
in one original string */
 
 static str
-pcre_replace(str *res, const char *origin_str, const char *pattern, const char 
*replacement, const char *flags)
+pcre_replace(str *res, const char *origin_str, const char *pattern,
+                        const char *replacement, const char *flags, bool 
global)
 {
 #ifdef HAVE_LIBPCRE
        const char *err_p = NULL;
        pcre *pcre_code = NULL;
        pcre_extra *extra;
        char *tmpres;
-       int i, j, k, len, errpos = 0, offset = 0;
+       char tmpbackref[4]; /* enough for 0 to 999 */
+       int i, j, k, m, len, errpos = 0, offset = 0;
        int compile_options = PCRE_UTF8, exec_options = PCRE_NOTEMPTY;
        int *ovector, ovecsize;
        int len_origin_str = (int) strlen(origin_str);
        int len_replacement = (int) strlen(replacement);
-       int capture_offsets[MAX_NR_CAPTURES * 2], ncaptures = 0, len_del = 0;
+       int len_replacement_with_backrefs = 0;
+       int nmatches = 0;
+       int capture_offsets[MAX_NR_MATCHES][MAX_NR_CAPTURES * 2], ncaptures = 
0, len_del = 0;
+       int backrefs[MAX_NR_CAPTURES] , backref_offsets[MAX_NR_CAPTURES * 2], 
nbackrefs = 0;
 
        while (*flags) {
                switch (*flags) {
@@ -584,13 +593,17 @@ pcre_replace(str *res, const char *origi
                        compile_options |= PCRE_EXTENDED;
                        break;
                default:
-                       throw(MAL, "pcre.replace", ILLEGAL_ARGUMENT ": 
unsupported flag character '%c'\n", *flags);
+                       throw(MAL, global ? "pcre.replace" : 
"pcre.replace_first",
+                                 ILLEGAL_ARGUMENT ": unsupported flag 
character '%c'\n",
+                                 *flags);
                }
                flags++;
        }
 
        if ((pcre_code = pcre_compile(pattern, compile_options, &err_p, 
&errpos, NULL)) == NULL) {
-               throw(MAL, "pcre.replace", OPERATION_FAILED ": pcre compile of 
pattern (%s) failed at %d with\n'%s'.\n", pattern, errpos, err_p);
+               throw(MAL, global ? "pcre.replace" : "pcre.replace_first",
+                         OPERATION_FAILED ": pcre compile of pattern (%s) 
failed at %d with\n'%s'.\n",
+                         pattern, errpos, err_p);
        }
 
        /* Since the compiled pattern is going to be used several times, it is
@@ -600,59 +613,110 @@ pcre_replace(str *res, const char *origi
        extra = pcre_study(pcre_code, 0, &err_p);
        if (err_p != NULL) {
                pcre_free(pcre_code);
-               throw(MAL, "pcre.replace", OPERATION_FAILED ": pcre study of 
pattern (%s) failed with '%s'.\n", pattern, err_p);
+               throw(MAL, global ? "pcre.replace" : "pcre.replace_first",
+                         OPERATION_FAILED ": pcre study of pattern (%s) failed 
with '%s'.\n",
+                         pattern, err_p);
        }
        pcre_fullinfo(pcre_code, extra, PCRE_INFO_CAPTURECOUNT, &i);
        ovecsize = (i + 1) * 3;
        if ((ovector = (int *) GDKmalloc(sizeof(int) * ovecsize)) == NULL) {
                pcre_free_study(extra);
                pcre_free(pcre_code);
-               throw(MAL, "pcre_replace", SQLSTATE(HY001) MAL_MALLOC_FAIL);
+               throw(MAL, global ? "pcre.replace" : "pcre.replace_first",
+                         SQLSTATE(HY001) MAL_MALLOC_FAIL);
        }
 
+       /* The pattern can match multiple substrings,
+        * and each substring can contain multiple captures.
+        * The first capture is always the entire matching substring.
+        */
        i = 0;
        do {
                j = pcre_exec(pcre_code, extra, origin_str, len_origin_str,
                                          offset, exec_options, ovector, 
ovecsize);
                if (j > 0) {
-                       capture_offsets[i] = ovector[0];
-                       capture_offsets[i+1] = ovector[1];
-                       ncaptures++;
-                       i += 2;
-                       len_del += (ovector[1] - ovector[0]);
+                       ncaptures = 0;
+                       for (i=0; (i < j) && (ncaptures < MAX_NR_CAPTURES); 
i++) {
+                               capture_offsets[nmatches][i*2] = ovector[i*2];
+                               capture_offsets[nmatches][i*2+1] = 
ovector[i*2+1];
+                               ncaptures++;
+                       }
                        offset = ovector[1];
+                       len_del += ovector[1] - ovector[0];
+                       nmatches++;
                }
-       } while (j > 0 && offset < len_origin_str && ncaptures < 
MAX_NR_CAPTURES);
+       } while (j > 0 && global && offset < len_origin_str && ncaptures < 
MAX_NR_CAPTURES);
        pcre_free_study(extra);
+       GDKfree(ovector);
 
-       if (ncaptures > 0) {
-               tmpres = GDKmalloc(len_origin_str - len_del + (len_replacement 
* ncaptures) + 1);
-               if (tmpres) {
-                       j = k = 0;
+       if (nmatches > 0) {
+               /* identify back references in the replacement string */
+               for (i=0; i<len_replacement; i++) {
+                       if (replacement[i] == '$' || replacement[i] == '\\') {
+                               if (i>0 && replacement[i-1] != '\\') {
+                                       for (k=0; i+k+1<len_replacement; k++) {
+                                               if (!isdigit((unsigned 
char)replacement[i+k+1]))
+                                                       break;
+                                       }
+                                       if (k>0) {
+                                               strncpy(tmpbackref, 
replacement+i+1, k);
+                                               tmpbackref[i+1+k] = '\0';
+                                               backrefs[nbackrefs] = 
atoi(tmpbackref);
+                                               backref_offsets[nbackrefs*2] = 
i;
+                                               backref_offsets[nbackrefs*2+1] 
= i+k+1;
+                                               nbackrefs++;
+                                       }
+                               }
+                       }
+               }
 
-                       /* possibly copy the substring before the first captured
-                        * substring */
-                       strncpy(tmpres, origin_str, capture_offsets[j]);
-                       k = capture_offsets[j];
-                       j++;
+               /* length of all replacements including backreferences */
+               len_replacement_with_backrefs = len_replacement * nmatches;
+               for (m=0; m<nmatches; m++) {
+                       for(i=0; i<nbackrefs; i++) {
+                               len_replacement_with_backrefs -= 
backref_offsets[i*2+1] - backref_offsets[i*2];
+                               if (backrefs[i] < ncaptures) {
+                                       len_replacement_with_backrefs += 
capture_offsets[m][backrefs[i]*2+1] - capture_offsets[m][backrefs[i]*2];
+                               }
+                       }
+               }
+               tmpres = GDKmalloc(len_origin_str - len_del + 
len_replacement_with_backrefs + 1);
+               if (tmpres) {
+                       k=0;
+                       for (m=0; m<nmatches; m++) {
+                               /* copy the substring before the matching 
substring */
+                               len = capture_offsets[m][0] - 
(m>0)*capture_offsets[m-1][1];
+                               strncpy(tmpres+k, 
origin_str+(m>0)*capture_offsets[m-1][1], len);
+                               k += len;
 
-                       for (i = 0; i < ncaptures - 1; i++) {
-                               strncpy(tmpres+k, replacement, len_replacement);
-                               k += len_replacement;
-                               /* copy the substring between two captured 
substrings */
-                               len = capture_offsets[j+1] - capture_offsets[j];
-                               strncpy(tmpres+k, 
origin_str+capture_offsets[j], len);
-                               k += len;
-                               j += 2;
+                               /* replace match */
+                               if (nbackrefs > 0) {
+                                       for(i=0; i<nbackrefs; i++) {
+                                               /* copy the replacement 
substring before the backreference */
+                                               len = backref_offsets[i*2] - 
(i>0)*backref_offsets[(i-1)*2+1];
+                                               strncpy(tmpres+k, 
replacement+(i>0)*backref_offsets[(i-1)*2+1], len);
+                                               k += len;
+                                               /* copy backreference */
+                                               if (backrefs[i] < ncaptures) {
+                                                       len = 
capture_offsets[m][backrefs[i]*2+1] - capture_offsets[m][backrefs[i]*2];
+                                                       strncpy(tmpres+k, 
origin_str+capture_offsets[m][backrefs[i]*2], len);
+                                                       k += len;
+                                               }
+                                       }
+                                       /* copy the replacement string after 
last backreference */
+                                       len = len_replacement - 
backref_offsets[(i-1)*2+1];
+                                       strncpy(tmpres+k, 
replacement+backref_offsets[(i-1)*2+1], len);
+                                       k += len;
+                               } else {
+                                       /* no backreferences, copy the entire 
replacement */
+                                       strncpy(tmpres+k, replacement, 
len_replacement);
+                                       k += len_replacement;
+                               }
                        }
 
-                       /* replace the last captured substring */
-                       strncpy(tmpres+k, replacement, len_replacement);
-                       k += len_replacement;
-                       /* possibly copy the substring after the last captured 
substring */
-                       len = len_origin_str - capture_offsets[j];
-                       strncpy(tmpres+k, origin_str+capture_offsets[j], len);
-                       k += len;
+                       /* copy the substring after the last matching substring 
*/
+                       strncpy(tmpres+k, origin_str+capture_offsets[m-1][1], 
len_origin_str - capture_offsets[m-1][1]);
+                       k += len_origin_str - capture_offsets[m-1][1];
                        tmpres[k] = '\0';
                }
        } else {
@@ -661,9 +725,9 @@ pcre_replace(str *res, const char *origi
        }
 
        pcre_free(pcre_code);
-       GDKfree(ovector);
        if (tmpres == NULL)
-               throw(MAL, "pcre.replace", SQLSTATE(HY001) MAL_MALLOC_FAIL);
+               throw(MAL, global ? "pcre.replace" : "pcre.replace_first",
+                         SQLSTATE(HY001) MAL_MALLOC_FAIL);
        *res = tmpres;
        return MAL_SUCCEED;
 #else
@@ -672,17 +736,22 @@ pcre_replace(str *res, const char *origi
        (void) pattern;
        (void) replacement;
        (void) flags;
-       throw(MAL, "pcre.replace", "Database was compiled without PCRE 
support.");
+       (void) global;
+       throw(MAL, global ? "pcre.replace" : "pcre.replace_first",
+                 "Database was compiled without PCRE support.");
 #endif
 }
 
_______________________________________________
checkin-list mailing list
[email protected]
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to