| // Copyright (c) 2010, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| // |
| // Author: Sanjay Ghemawat |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <limits.h> /* for SHRT_MIN, USHRT_MAX, etc */ |
| #include <string.h> /* for memcpy */ |
| #include <assert.h> |
| #include <errno.h> |
| #include <string> |
| #include <algorithm> |
| |
| #include "pcrecpp_internal.h" |
| #include "pcre.h" |
| #include "pcrecpp.h" |
| #include "pcre_stringpiece.h" |
| |
| |
| namespace pcrecpp { |
| |
| // Maximum number of args we can set |
| static const int kMaxArgs = 16; |
| static const int kVecSize = (1 + kMaxArgs) * 3; // results + PCRE workspace |
| |
| // Special object that stands-in for no argument |
| Arg RE::no_arg((void*)NULL); |
| |
| // This is for ABI compatibility with old versions of pcre (pre-7.6), |
| // which defined a global no_arg variable instead of putting it in the |
| // RE class. This works on GCC >= 3, at least. It definitely works |
| // for ELF, but may not for other object formats (Mach-O, for |
| // instance, does not support aliases.) We could probably have a more |
| // inclusive test if we ever needed it. (Note that not only the |
| // __attribute__ syntax, but also __USER_LABEL_PREFIX__, are |
| // gnu-specific.) |
| #if defined(__GNUC__) && __GNUC__ >= 3 && defined(__ELF__) |
| # define ULP_AS_STRING(x) ULP_AS_STRING_INTERNAL(x) |
| # define ULP_AS_STRING_INTERNAL(x) #x |
| # define USER_LABEL_PREFIX_STR ULP_AS_STRING(__USER_LABEL_PREFIX__) |
| extern Arg no_arg |
| __attribute__((alias(USER_LABEL_PREFIX_STR "_ZN7pcrecpp2RE6no_argE"))); |
| #endif |
| |
| // If a regular expression has no error, its error_ field points here |
| static const string empty_string; |
| |
| // If the user doesn't ask for any options, we just use this one |
| static RE_Options default_options; |
| |
| void RE::Init(const string& pat, const RE_Options* options) { |
| pattern_ = pat; |
| if (options == NULL) { |
| options_ = default_options; |
| } else { |
| options_ = *options; |
| } |
| error_ = &empty_string; |
| re_full_ = NULL; |
| re_partial_ = NULL; |
| |
| re_partial_ = Compile(UNANCHORED); |
| if (re_partial_ != NULL) { |
| re_full_ = Compile(ANCHOR_BOTH); |
| } |
| } |
| |
| void RE::Cleanup() { |
| if (re_full_ != NULL) (*pcre_free)(re_full_); |
| if (re_partial_ != NULL) (*pcre_free)(re_partial_); |
| if (error_ != &empty_string) delete error_; |
| } |
| |
| |
| RE::~RE() { |
| Cleanup(); |
| } |
| |
| |
| pcre* RE::Compile(Anchor anchor) { |
| // First, convert RE_Options into pcre options |
| int pcre_options = 0; |
| pcre_options = options_.all_options(); |
| |
| // Special treatment for anchoring. This is needed because at |
| // runtime pcre only provides an option for anchoring at the |
| // beginning of a string (unless you use offset). |
| // |
| // There are three types of anchoring we want: |
| // UNANCHORED Compile the original pattern, and use |
| // a pcre unanchored match. |
| // ANCHOR_START Compile the original pattern, and use |
| // a pcre anchored match. |
| // ANCHOR_BOTH Tack a "\z" to the end of the original pattern |
| // and use a pcre anchored match. |
| |
| const char* compile_error; |
| int eoffset; |
| pcre* re; |
| if (anchor != ANCHOR_BOTH) { |
| re = pcre_compile(pattern_.c_str(), pcre_options, |
| &compile_error, &eoffset, NULL); |
| } else { |
| // Tack a '\z' at the end of RE. Parenthesize it first so that |
| // the '\z' applies to all top-level alternatives in the regexp. |
| string wrapped = "(?:"; // A non-counting grouping operator |
| wrapped += pattern_; |
| wrapped += ")\\z"; |
| re = pcre_compile(wrapped.c_str(), pcre_options, |
| &compile_error, &eoffset, NULL); |
| } |
| if (re == NULL) { |
| if (error_ == &empty_string) error_ = new string(compile_error); |
| } |
| return re; |
| } |
| |
| /***** Matching interfaces *****/ |
| |
| bool RE::FullMatch(const StringPiece& text, |
| const Arg& ptr1, |
| const Arg& ptr2, |
| const Arg& ptr3, |
| const Arg& ptr4, |
| const Arg& ptr5, |
| const Arg& ptr6, |
| const Arg& ptr7, |
| const Arg& ptr8, |
| const Arg& ptr9, |
| const Arg& ptr10, |
| const Arg& ptr11, |
| const Arg& ptr12, |
| const Arg& ptr13, |
| const Arg& ptr14, |
| const Arg& ptr15, |
| const Arg& ptr16) const { |
| const Arg* args[kMaxArgs]; |
| int n = 0; |
| if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; |
| if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; |
| if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; |
| if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; |
| if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; |
| if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; |
| if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; |
| if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; |
| if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; |
| if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; |
| if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; |
| if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; |
| if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; |
| if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; |
| if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; |
| if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; |
| done: |
| |
| int consumed; |
| int vec[kVecSize]; |
| return DoMatchImpl(text, ANCHOR_BOTH, &consumed, args, n, vec, kVecSize); |
| } |
| |
| bool RE::PartialMatch(const StringPiece& text, |
| const Arg& ptr1, |
| const Arg& ptr2, |
| const Arg& ptr3, |
| const Arg& ptr4, |
| const Arg& ptr5, |
| const Arg& ptr6, |
| const Arg& ptr7, |
| const Arg& ptr8, |
| const Arg& ptr9, |
| const Arg& ptr10, |
| const Arg& ptr11, |
| const Arg& ptr12, |
| const Arg& ptr13, |
| const Arg& ptr14, |
| const Arg& ptr15, |
| const Arg& ptr16) const { |
| const Arg* args[kMaxArgs]; |
| int n = 0; |
| if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; |
| if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; |
| if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; |
| if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; |
| if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; |
| if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; |
| if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; |
| if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; |
| if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; |
| if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; |
| if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; |
| if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; |
| if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; |
| if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; |
| if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; |
| if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; |
| done: |
| |
| int consumed; |
| int vec[kVecSize]; |
| return DoMatchImpl(text, UNANCHORED, &consumed, args, n, vec, kVecSize); |
| } |
| |
| bool RE::Consume(StringPiece* input, |
| const Arg& ptr1, |
| const Arg& ptr2, |
| const Arg& ptr3, |
| const Arg& ptr4, |
| const Arg& ptr5, |
| const Arg& ptr6, |
| const Arg& ptr7, |
| const Arg& ptr8, |
| const Arg& ptr9, |
| const Arg& ptr10, |
| const Arg& ptr11, |
| const Arg& ptr12, |
| const Arg& ptr13, |
| const Arg& ptr14, |
| const Arg& ptr15, |
| const Arg& ptr16) const { |
| const Arg* args[kMaxArgs]; |
| int n = 0; |
| if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; |
| if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; |
| if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; |
| if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; |
| if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; |
| if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; |
| if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; |
| if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; |
| if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; |
| if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; |
| if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; |
| if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; |
| if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; |
| if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; |
| if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; |
| if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; |
| done: |
| |
| int consumed; |
| int vec[kVecSize]; |
| if (DoMatchImpl(*input, ANCHOR_START, &consumed, |
| args, n, vec, kVecSize)) { |
| input->remove_prefix(consumed); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| bool RE::FindAndConsume(StringPiece* input, |
| const Arg& ptr1, |
| const Arg& ptr2, |
| const Arg& ptr3, |
| const Arg& ptr4, |
| const Arg& ptr5, |
| const Arg& ptr6, |
| const Arg& ptr7, |
| const Arg& ptr8, |
| const Arg& ptr9, |
| const Arg& ptr10, |
| const Arg& ptr11, |
| const Arg& ptr12, |
| const Arg& ptr13, |
| const Arg& ptr14, |
| const Arg& ptr15, |
| const Arg& ptr16) const { |
| const Arg* args[kMaxArgs]; |
| int n = 0; |
| if (&ptr1 == &no_arg) goto done; args[n++] = &ptr1; |
| if (&ptr2 == &no_arg) goto done; args[n++] = &ptr2; |
| if (&ptr3 == &no_arg) goto done; args[n++] = &ptr3; |
| if (&ptr4 == &no_arg) goto done; args[n++] = &ptr4; |
| if (&ptr5 == &no_arg) goto done; args[n++] = &ptr5; |
| if (&ptr6 == &no_arg) goto done; args[n++] = &ptr6; |
| if (&ptr7 == &no_arg) goto done; args[n++] = &ptr7; |
| if (&ptr8 == &no_arg) goto done; args[n++] = &ptr8; |
| if (&ptr9 == &no_arg) goto done; args[n++] = &ptr9; |
| if (&ptr10 == &no_arg) goto done; args[n++] = &ptr10; |
| if (&ptr11 == &no_arg) goto done; args[n++] = &ptr11; |
| if (&ptr12 == &no_arg) goto done; args[n++] = &ptr12; |
| if (&ptr13 == &no_arg) goto done; args[n++] = &ptr13; |
| if (&ptr14 == &no_arg) goto done; args[n++] = &ptr14; |
| if (&ptr15 == &no_arg) goto done; args[n++] = &ptr15; |
| if (&ptr16 == &no_arg) goto done; args[n++] = &ptr16; |
| done: |
| |
| int consumed; |
| int vec[kVecSize]; |
| if (DoMatchImpl(*input, UNANCHORED, &consumed, |
| args, n, vec, kVecSize)) { |
| input->remove_prefix(consumed); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| bool RE::Replace(const StringPiece& rewrite, |
| string *str) const { |
| int vec[kVecSize]; |
| int matches = TryMatch(*str, 0, UNANCHORED, true, vec, kVecSize); |
| if (matches == 0) |
| return false; |
| |
| string s; |
| if (!Rewrite(&s, rewrite, *str, vec, matches)) |
| return false; |
| |
| assert(vec[0] >= 0); |
| assert(vec[1] >= 0); |
| str->replace(vec[0], vec[1] - vec[0], s); |
| return true; |
| } |
| |
| // Returns PCRE_NEWLINE_CRLF, PCRE_NEWLINE_CR, or PCRE_NEWLINE_LF. |
| // Note that PCRE_NEWLINE_CRLF is defined to be P_N_CR | P_N_LF. |
| // Modified by PH to add PCRE_NEWLINE_ANY and PCRE_NEWLINE_ANYCRLF. |
| |
| static int NewlineMode(int pcre_options) { |
| // TODO: if we can make it threadsafe, cache this var |
| int newline_mode = 0; |
| /* if (newline_mode) return newline_mode; */ // do this once it's cached |
| if (pcre_options & (PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF| |
| PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF)) { |
| newline_mode = (pcre_options & |
| (PCRE_NEWLINE_CRLF|PCRE_NEWLINE_CR|PCRE_NEWLINE_LF| |
| PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF)); |
| } else { |
| int newline; |
| pcre_config(PCRE_CONFIG_NEWLINE, &newline); |
| if (newline == 10) |
| newline_mode = PCRE_NEWLINE_LF; |
| else if (newline == 13) |
| newline_mode = PCRE_NEWLINE_CR; |
| else if (newline == 3338) |
| newline_mode = PCRE_NEWLINE_CRLF; |
| else if (newline == -1) |
| newline_mode = PCRE_NEWLINE_ANY; |
| else if (newline == -2) |
| newline_mode = PCRE_NEWLINE_ANYCRLF; |
| else |
| assert(NULL == "Unexpected return value from pcre_config(NEWLINE)"); |
| } |
| return newline_mode; |
| } |
| |
| int RE::GlobalReplace(const StringPiece& rewrite, |
| string *str) const { |
| int count = 0; |
| int vec[kVecSize]; |
| string out; |
| int start = 0; |
| int lastend = -1; |
| bool last_match_was_empty_string = false; |
| |
| while (start <= static_cast<int>(str->length())) { |
| // If the previous match was for the empty string, we shouldn't |
| // just match again: we'll match in the same way and get an |
| // infinite loop. Instead, we do the match in a special way: |
| // anchored -- to force another try at the same position -- |
| // and with a flag saying that this time, ignore empty matches. |
| // If this special match returns, that means there's a non-empty |
| // match at this position as well, and we can continue. If not, |
| // we do what perl does, and just advance by one. |
| // Notice that perl prints '@@@' for this; |
| // perl -le '$_ = "aa"; s/b*|aa/@/g; print' |
| int matches; |
| if (last_match_was_empty_string) { |
| matches = TryMatch(*str, start, ANCHOR_START, false, vec, kVecSize); |
| if (matches <= 0) { |
| int matchend = start + 1; // advance one character. |
| // If the current char is CR and we're in CRLF mode, skip LF too. |
| // Note it's better to call pcre_fullinfo() than to examine |
| // all_options(), since options_ could have changed bewteen |
| // compile-time and now, but this is simpler and safe enough. |
| // Modified by PH to add ANY and ANYCRLF. |
| if (matchend < static_cast<int>(str->length()) && |
| (*str)[start] == '\r' && (*str)[matchend] == '\n' && |
| (NewlineMode(options_.all_options()) == PCRE_NEWLINE_CRLF || |
| NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANY || |
| NewlineMode(options_.all_options()) == PCRE_NEWLINE_ANYCRLF)) { |
| matchend++; |
| } |
| // We also need to advance more than one char if we're in utf8 mode. |
| #ifdef SUPPORT_UTF8 |
| if (options_.utf8()) { |
| while (matchend < static_cast<int>(str->length()) && |
| ((*str)[matchend] & 0xc0) == 0x80) |
| matchend++; |
| } |
| #endif |
| if (start < static_cast<int>(str->length())) |
| out.append(*str, start, matchend - start); |
| start = matchend; |
| last_match_was_empty_string = false; |
| continue; |
| } |
| } else { |
| matches = TryMatch(*str, start, UNANCHORED, true, vec, kVecSize); |
| if (matches <= 0) |
| break; |
| } |
| int matchstart = vec[0], matchend = vec[1]; |
| assert(matchstart >= start); |
| assert(matchend >= matchstart); |
| out.append(*str, start, matchstart - start); |
| Rewrite(&out, rewrite, *str, vec, matches); |
| start = matchend; |
| lastend = matchend; |
| count++; |
| last_match_was_empty_string = (matchstart == matchend); |
| } |
| |
| if (count == 0) |
| return 0; |
| |
| if (start < static_cast<int>(str->length())) |
| out.append(*str, start, str->length() - start); |
| swap(out, *str); |
| return count; |
| } |
| |
| bool RE::Extract(const StringPiece& rewrite, |
| const StringPiece& text, |
| string *out) const { |
| int vec[kVecSize]; |
| int matches = TryMatch(text, 0, UNANCHORED, true, vec, kVecSize); |
| if (matches == 0) |
| return false; |
| out->erase(); |
| return Rewrite(out, rewrite, text, vec, matches); |
| } |
| |
| /*static*/ string RE::QuoteMeta(const StringPiece& unquoted) { |
| string result; |
| |
| // Escape any ascii character not in [A-Za-z_0-9]. |
| // |
| // Note that it's legal to escape a character even if it has no |
| // special meaning in a regular expression -- so this function does |
| // that. (This also makes it identical to the perl function of the |
| // same name; see `perldoc -f quotemeta`.) The one exception is |
| // escaping NUL: rather than doing backslash + NUL, like perl does, |
| // we do '\0', because pcre itself doesn't take embedded NUL chars. |
| for (int ii = 0; ii < unquoted.size(); ++ii) { |
| // Note that using 'isalnum' here raises the benchmark time from |
| // 32ns to 58ns: |
| if (unquoted[ii] == '\0') { |
| result += "\\0"; |
| } else if ((unquoted[ii] < 'a' || unquoted[ii] > 'z') && |
| (unquoted[ii] < 'A' || unquoted[ii] > 'Z') && |
| (unquoted[ii] < '0' || unquoted[ii] > '9') && |
| unquoted[ii] != '_' && |
| // If this is the part of a UTF8 or Latin1 character, we need |
| // to copy this byte without escaping. Experimentally this is |
| // what works correctly with the regexp library. |
| !(unquoted[ii] & 128)) { |
| result += '\\'; |
| result += unquoted[ii]; |
| } else { |
| result += unquoted[ii]; |
| } |
| } |
| |
| return result; |
| } |
| |
| /***** Actual matching and rewriting code *****/ |
| |
| int RE::TryMatch(const StringPiece& text, |
| int startpos, |
| Anchor anchor, |
| bool empty_ok, |
| int *vec, |
| int vecsize) const { |
| pcre* re = (anchor == ANCHOR_BOTH) ? re_full_ : re_partial_; |
| if (re == NULL) { |
| //fprintf(stderr, "Matching against invalid re: %s\n", error_->c_str()); |
| return 0; |
| } |
| |
| pcre_extra extra = { 0, 0, 0, 0, 0, 0 }; |
| if (options_.match_limit() > 0) { |
| extra.flags |= PCRE_EXTRA_MATCH_LIMIT; |
| extra.match_limit = options_.match_limit(); |
| } |
| if (options_.match_limit_recursion() > 0) { |
| extra.flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION; |
| extra.match_limit_recursion = options_.match_limit_recursion(); |
| } |
| |
| int options = 0; |
| if (anchor != UNANCHORED) |
| options |= PCRE_ANCHORED; |
| if (!empty_ok) |
| options |= PCRE_NOTEMPTY; |
| |
| int rc = pcre_exec(re, // The regular expression object |
| &extra, |
| (text.data() == NULL) ? "" : text.data(), |
| text.size(), |
| startpos, |
| options, |
| vec, |
| vecsize); |
| |
| // Handle errors |
| if (rc == PCRE_ERROR_NOMATCH) { |
| return 0; |
| } else if (rc < 0) { |
| //fprintf(stderr, "Unexpected return code: %d when matching '%s'\n", |
| // re, pattern_.c_str()); |
| return 0; |
| } else if (rc == 0) { |
| // pcre_exec() returns 0 as a special case when the number of |
| // capturing subpatterns exceeds the size of the vector. |
| // When this happens, there is a match and the output vector |
| // is filled, but we miss out on the positions of the extra subpatterns. |
| rc = vecsize / 2; |
| } |
| |
| return rc; |
| } |
| |
| bool RE::DoMatchImpl(const StringPiece& text, |
| Anchor anchor, |
| int* consumed, |
| const Arg* const* args, |
| int n, |
| int* vec, |
| int vecsize) const { |
| assert((1 + n) * 3 <= vecsize); // results + PCRE workspace |
| int matches = TryMatch(text, 0, anchor, true, vec, vecsize); |
| assert(matches >= 0); // TryMatch never returns negatives |
| if (matches == 0) |
| return false; |
| |
| *consumed = vec[1]; |
| |
| if (n == 0 || args == NULL) { |
| // We are not interested in results |
| return true; |
| } |
| |
| if (NumberOfCapturingGroups() < n) { |
| // RE has fewer capturing groups than number of arg pointers passed in |
| return false; |
| } |
| |
| // If we got here, we must have matched the whole pattern. |
| // We do not need (can not do) any more checks on the value of 'matches' here |
| // -- see the comment for TryMatch. |
| for (int i = 0; i < n; i++) { |
| const int start = vec[2*(i+1)]; |
| const int limit = vec[2*(i+1)+1]; |
| if (!args[i]->Parse(text.data() + start, limit-start)) { |
| // TODO: Should we indicate what the error was? |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool RE::DoMatch(const StringPiece& text, |
| Anchor anchor, |
| int* consumed, |
| const Arg* const args[], |
| int n) const { |
| assert(n >= 0); |
| size_t const vecsize = (1 + n) * 3; // results + PCRE workspace |
| // (as for kVecSize) |
| int space[21]; // use stack allocation for small vecsize (common case) |
| int* vec = vecsize <= 21 ? space : new int[vecsize]; |
| bool retval = DoMatchImpl(text, anchor, consumed, args, n, vec, (int)vecsize); |
| if (vec != space) delete [] vec; |
| return retval; |
| } |
| |
| bool RE::Rewrite(string *out, const StringPiece &rewrite, |
| const StringPiece &text, int *vec, int veclen) const { |
| for (const char *s = rewrite.data(), *end = s + rewrite.size(); |
| s < end; s++) { |
| int c = *s; |
| if (c == '\\') { |
| c = *++s; |
| if (isdigit(c)) { |
| int n = (c - '0'); |
| if (n >= veclen) { |
| //fprintf(stderr, requested group %d in regexp %.*s\n", |
| // n, rewrite.size(), rewrite.data()); |
| return false; |
| } |
| int start = vec[2 * n]; |
| if (start >= 0) |
| out->append(text.data() + start, vec[2 * n + 1] - start); |
| } else if (c == '\\') { |
| *out += '\\'; |
| } else { |
| //fprintf(stderr, "invalid rewrite pattern: %.*s\n", |
| // rewrite.size(), rewrite.data()); |
| return false; |
| } |
| } else { |
| *out += c; |
| } |
| } |
| return true; |
| } |
| |
| // Return the number of capturing subpatterns, or -1 if the |
| // regexp wasn't valid on construction. |
| int RE::NumberOfCapturingGroups() const { |
| if (re_partial_ == NULL) return -1; |
| |
| int result; |
| int pcre_retval = pcre_fullinfo(re_partial_, // The regular expression object |
| NULL, // We did not study the pattern |
| PCRE_INFO_CAPTURECOUNT, |
| &result); |
| assert(pcre_retval == 0); |
| return result; |
| } |
| |
| /***** Parsers for various types *****/ |
| |
| bool Arg::parse_null(const char* str, int n, void* dest) { |
| // We fail if somebody asked us to store into a non-NULL void* pointer |
| return (dest == NULL); |
| } |
| |
| bool Arg::parse_string(const char* str, int n, void* dest) { |
| if (dest == NULL) return true; |
| reinterpret_cast<string*>(dest)->assign(str, n); |
| return true; |
| } |
| |
| bool Arg::parse_stringpiece(const char* str, int n, void* dest) { |
| if (dest == NULL) return true; |
| reinterpret_cast<StringPiece*>(dest)->set(str, n); |
| return true; |
| } |
| |
| bool Arg::parse_char(const char* str, int n, void* dest) { |
| if (n != 1) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<char*>(dest)) = str[0]; |
| return true; |
| } |
| |
| bool Arg::parse_uchar(const char* str, int n, void* dest) { |
| if (n != 1) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<unsigned char*>(dest)) = str[0]; |
| return true; |
| } |
| |
| // Largest number spec that we are willing to parse |
| static const int kMaxNumberLength = 32; |
| |
| // REQUIRES "buf" must have length at least kMaxNumberLength+1 |
| // REQUIRES "n > 0" |
| // Copies "str" into "buf" and null-terminates if necessary. |
| // Returns one of: |
| // a. "str" if no termination is needed |
| // b. "buf" if the string was copied and null-terminated |
| // c. "" if the input was invalid and has no hope of being parsed |
| static const char* TerminateNumber(char* buf, const char* str, int n) { |
| if ((n > 0) && isspace(*str)) { |
| // We are less forgiving than the strtoxxx() routines and do not |
| // allow leading spaces. |
| return ""; |
| } |
| |
| // See if the character right after the input text may potentially |
| // look like a digit. |
| if (isdigit(str[n]) || |
| ((str[n] >= 'a') && (str[n] <= 'f')) || |
| ((str[n] >= 'A') && (str[n] <= 'F'))) { |
| if (n > kMaxNumberLength) return ""; // Input too big to be a valid number |
| memcpy(buf, str, n); |
| buf[n] = '\0'; |
| return buf; |
| } else { |
| // We can parse right out of the supplied string, so return it. |
| return str; |
| } |
| } |
| |
| bool Arg::parse_long_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| if (n == 0) return false; |
| char buf[kMaxNumberLength+1]; |
| str = TerminateNumber(buf, str, n); |
| char* end; |
| errno = 0; |
| long r = strtol(str, &end, radix); |
| if (end != str + n) return false; // Leftover junk |
| if (errno) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<long*>(dest)) = r; |
| return true; |
| } |
| |
| bool Arg::parse_ulong_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| if (n == 0) return false; |
| char buf[kMaxNumberLength+1]; |
| str = TerminateNumber(buf, str, n); |
| if (str[0] == '-') return false; // strtoul() on a negative number?! |
| char* end; |
| errno = 0; |
| unsigned long r = strtoul(str, &end, radix); |
| if (end != str + n) return false; // Leftover junk |
| if (errno) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<unsigned long*>(dest)) = r; |
| return true; |
| } |
| |
| bool Arg::parse_short_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| long r; |
| if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse |
| if (r < SHRT_MIN || r > SHRT_MAX) return false; // Out of range |
| if (dest == NULL) return true; |
| *(reinterpret_cast<short*>(dest)) = static_cast<short>(r); |
| return true; |
| } |
| |
| bool Arg::parse_ushort_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| unsigned long r; |
| if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse |
| if (r > USHRT_MAX) return false; // Out of range |
| if (dest == NULL) return true; |
| *(reinterpret_cast<unsigned short*>(dest)) = static_cast<unsigned short>(r); |
| return true; |
| } |
| |
| bool Arg::parse_int_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| long r; |
| if (!parse_long_radix(str, n, &r, radix)) return false; // Could not parse |
| if (r < INT_MIN || r > INT_MAX) return false; // Out of range |
| if (dest == NULL) return true; |
| *(reinterpret_cast<int*>(dest)) = r; |
| return true; |
| } |
| |
| bool Arg::parse_uint_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| unsigned long r; |
| if (!parse_ulong_radix(str, n, &r, radix)) return false; // Could not parse |
| if (r > UINT_MAX) return false; // Out of range |
| if (dest == NULL) return true; |
| *(reinterpret_cast<unsigned int*>(dest)) = r; |
| return true; |
| } |
| |
| bool Arg::parse_longlong_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| #ifndef HAVE_LONG_LONG |
| return false; |
| #else |
| if (n == 0) return false; |
| char buf[kMaxNumberLength+1]; |
| str = TerminateNumber(buf, str, n); |
| char* end; |
| errno = 0; |
| #if defined HAVE_STRTOQ |
| long long r = strtoq(str, &end, radix); |
| #elif defined HAVE_STRTOLL |
| long long r = strtoll(str, &end, radix); |
| #elif defined HAVE__STRTOI64 |
| long long r = _strtoi64(str, &end, radix); |
| #elif defined HAVE_STRTOIMAX |
| long long r = strtoimax(str, &end, radix); |
| #else |
| #error parse_longlong_radix: cannot convert input to a long-long |
| #endif |
| if (end != str + n) return false; // Leftover junk |
| if (errno) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<long long*>(dest)) = r; |
| return true; |
| #endif /* HAVE_LONG_LONG */ |
| } |
| |
| bool Arg::parse_ulonglong_radix(const char* str, |
| int n, |
| void* dest, |
| int radix) { |
| #ifndef HAVE_UNSIGNED_LONG_LONG |
| return false; |
| #else |
| if (n == 0) return false; |
| char buf[kMaxNumberLength+1]; |
| str = TerminateNumber(buf, str, n); |
| if (str[0] == '-') return false; // strtoull() on a negative number?! |
| char* end; |
| errno = 0; |
| #if defined HAVE_STRTOQ |
| unsigned long long r = strtouq(str, &end, radix); |
| #elif defined HAVE_STRTOLL |
| unsigned long long r = strtoull(str, &end, radix); |
| #elif defined HAVE__STRTOI64 |
| unsigned long long r = _strtoui64(str, &end, radix); |
| #elif defined HAVE_STRTOIMAX |
| unsigned long long r = strtoumax(str, &end, radix); |
| #else |
| #error parse_ulonglong_radix: cannot convert input to a long-long |
| #endif |
| if (end != str + n) return false; // Leftover junk |
| if (errno) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<unsigned long long*>(dest)) = r; |
| return true; |
| #endif /* HAVE_UNSIGNED_LONG_LONG */ |
| } |
| |
| bool Arg::parse_double(const char* str, int n, void* dest) { |
| if (n == 0) return false; |
| static const int kMaxLength = 200; |
| char buf[kMaxLength]; |
| if (n >= kMaxLength) return false; |
| memcpy(buf, str, n); |
| buf[n] = '\0'; |
| errno = 0; |
| char* end; |
| double r = strtod(buf, &end); |
| if (end != buf + n) return false; // Leftover junk |
| if (errno) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<double*>(dest)) = r; |
| return true; |
| } |
| |
| bool Arg::parse_float(const char* str, int n, void* dest) { |
| double r; |
| if (!parse_double(str, n, &r)) return false; |
| if (dest == NULL) return true; |
| *(reinterpret_cast<float*>(dest)) = static_cast<float>(r); |
| return true; |
| } |
| |
| |
| #define DEFINE_INTEGER_PARSERS(name) \ |
| bool Arg::parse_##name(const char* str, int n, void* dest) { \ |
| return parse_##name##_radix(str, n, dest, 10); \ |
| } \ |
| bool Arg::parse_##name##_hex(const char* str, int n, void* dest) { \ |
| return parse_##name##_radix(str, n, dest, 16); \ |
| } \ |
| bool Arg::parse_##name##_octal(const char* str, int n, void* dest) { \ |
| return parse_##name##_radix(str, n, dest, 8); \ |
| } \ |
| bool Arg::parse_##name##_cradix(const char* str, int n, void* dest) { \ |
| return parse_##name##_radix(str, n, dest, 0); \ |
| } |
| |
| DEFINE_INTEGER_PARSERS(short) /* */ |
| DEFINE_INTEGER_PARSERS(ushort) /* */ |
| DEFINE_INTEGER_PARSERS(int) /* Don't use semicolons after these */ |
| DEFINE_INTEGER_PARSERS(uint) /* statements because they can cause */ |
| DEFINE_INTEGER_PARSERS(long) /* compiler warnings if the checking */ |
| DEFINE_INTEGER_PARSERS(ulong) /* level is turned up high enough. */ |
| DEFINE_INTEGER_PARSERS(longlong) /* */ |
| DEFINE_INTEGER_PARSERS(ulonglong) /* */ |
| |
| #undef DEFINE_INTEGER_PARSERS |
| |
| } // namespace pcrecpp |