-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CCBC-1384: update parser for golang duration
Now it follows golang behaviour more closely Change-Id: I964e89710b40375a37497a07854008bfc3c19e51 Reviewed-on: http://review.couchbase.org/c/libcouchbase/+/155445 Tested-by: Build Bot <build@couchbase.com> Reviewed-by: Brett Lawson <brett19@gmail.com>
- Loading branch information
Showing
6 changed files
with
260 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ | ||
/* | ||
* Copyright 2016-2021 Couchbase, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#include "query_utils.hh" | ||
|
||
/** | ||
* leading_int consumes the leading [0-9]* from s. | ||
*/ | ||
static bool leading_int(std::string &s, std::int64_t &v) | ||
{ | ||
v = 0; | ||
std::size_t i = 0; | ||
for (; i < s.size(); ++i) { | ||
auto c = s[i]; | ||
|
||
if (c < '0' || c > '9') { | ||
break; | ||
} | ||
|
||
if (v > std::int64_t((1LLU << 63U) - 1LLU) / 10) { | ||
return false; | ||
} | ||
|
||
v = v * 10 + std::int64_t(c) - '0'; | ||
|
||
if (v < 0) { | ||
return false; | ||
} | ||
} | ||
s = s.substr(i); | ||
return true; | ||
} | ||
|
||
/** | ||
* leading_fraction consumes the leading [0-9]* from s. | ||
* | ||
* It is used only for fractions, so does not return an error on overflow, | ||
* it just stops accumulating precision. | ||
*/ | ||
|
||
static void leading_fraction(std::string &s, std::int64_t &x, std::uint32_t &scale) | ||
{ | ||
std::size_t i = 0; | ||
scale = 1; | ||
|
||
bool overflow = false; | ||
|
||
for (; i < s.size(); ++i) { | ||
auto c = s[i]; | ||
|
||
if (c < '0' || c > '9') { | ||
break; | ||
} | ||
|
||
if (overflow) { | ||
continue; | ||
} | ||
|
||
if (x > std::int64_t((1LLU << 63LLU) - 1LLU) / 10) { | ||
// It's possible for overflow to give a positive number, so take care. | ||
overflow = true; | ||
continue; | ||
} | ||
|
||
auto y = x * 10 + std::int64_t(c) - '0'; | ||
if (y < 0) { | ||
overflow = true; | ||
continue; | ||
} | ||
|
||
x = y; | ||
scale *= 10; | ||
} | ||
|
||
s = s.substr(i); | ||
} | ||
|
||
/** | ||
* Parses a duration string. | ||
* A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, | ||
* such as "300ms", "-1.5h" or "2h45m". | ||
* | ||
* Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". | ||
*/ | ||
std::chrono::nanoseconds lcb_parse_golang_duration(const std::string &text) | ||
{ | ||
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ | ||
std::string s = text; | ||
std::chrono::nanoseconds d{0}; | ||
bool neg{false}; | ||
|
||
// Consume [-+]? | ||
if (!s.empty()) { | ||
auto c = s[0]; | ||
|
||
if (c == '-' || c == '+') { | ||
neg = c == '-'; | ||
s = s.substr(1); | ||
} | ||
} | ||
if (neg) { | ||
throw lcb_duration_parse_error("negative durations are not supported: " + text); | ||
} | ||
|
||
// Special case: if all that is left is "0", this is zero. | ||
if (s == "0") { | ||
return std::chrono::nanoseconds::zero(); | ||
} | ||
|
||
if (s.empty()) { | ||
throw lcb_duration_parse_error("invalid duration: " + text); | ||
} | ||
|
||
while (!s.empty()) { | ||
// The next character must be [0-9.] | ||
if (!(s[0] == '.' || ('0' <= s[0] && s[0] <= '9'))) { | ||
throw lcb_duration_parse_error("invalid duration: " + text); | ||
} | ||
|
||
// Consume [0-9]* | ||
auto pl = s.size(); | ||
|
||
std::int64_t v{0}; // integer before decimal point | ||
if (!leading_int(s, v)) { | ||
throw lcb_duration_parse_error("invalid duration (leading_int overflow): " + text); | ||
} | ||
|
||
bool pre = pl != s.size(); // whether we consumed anything before a period | ||
|
||
std::int64_t f{0}; // integer after decimal point | ||
std::uint32_t scale{1}; // value = v + f/scale | ||
|
||
// Consume (\.[0-9]*)? | ||
bool post = false; | ||
if (!s.empty() && s[0] == '.') { | ||
s = s.substr(1); | ||
pl = s.size(); | ||
leading_fraction(s, f, scale); | ||
post = pl != s.size(); | ||
} | ||
|
||
if (!pre && !post) { | ||
// no digits (e.g. ".s" or "-.s") | ||
throw lcb_duration_parse_error("invalid duration: " + text); | ||
} | ||
|
||
// Consume unit. | ||
std::size_t i = 0; | ||
for (; i < s.size(); ++i) { | ||
auto c = s[i]; | ||
if (c == '.' || ('0' <= c && c <= '9')) { | ||
break; | ||
} | ||
} | ||
if (i == 0) { | ||
throw lcb_duration_parse_error("missing unit in duration: " + text); | ||
} | ||
|
||
auto u = s.substr(0, i); | ||
s = s.substr(i); | ||
|
||
if (u == "ns") { | ||
d += std::chrono::nanoseconds(v); /* no sub-nanoseconds, ignore 'f' */ | ||
} else if (u == "us" || u == "µs" /* U+00B5 = micro symbol */ || u == "μs" /* U+03BC = Greek letter mu */) { | ||
d += std::chrono::microseconds(v) + | ||
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(f)) / scale; | ||
} else if (u == "ms") { | ||
d += std::chrono::milliseconds(v) + | ||
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(f)) / scale; | ||
} else if (u == "s") { | ||
d += std::chrono::seconds(v) + | ||
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(f)) / scale; | ||
} else if (u == "m") { | ||
d += std::chrono::minutes(v) + | ||
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::minutes(f)) / scale; | ||
} else if (u == "h") { | ||
d += std::chrono::hours(v) + | ||
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::hours(f)) / scale; | ||
} else { | ||
throw lcb_duration_parse_error(std::string("unknown unit ").append(u).append(" in duration ").append(text)); | ||
} | ||
} | ||
|
||
return d; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ | ||
/* | ||
* Copyright 2016-2021 Couchbase, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#ifndef LIBCOUCHBASE_N1QL_QUERY_UTILS_HH | ||
#define LIBCOUCHBASE_N1QL_QUERY_UTILS_HH | ||
|
||
#include <cstddef> | ||
#include <cstdint> | ||
#include <chrono> | ||
#include <string> | ||
#include <stdexcept> | ||
|
||
/** | ||
* @private | ||
*/ | ||
class lcb_duration_parse_error : public std::runtime_error | ||
{ | ||
public: | ||
explicit lcb_duration_parse_error(const std::string &msg) : std::runtime_error(msg) {} | ||
}; | ||
|
||
/** | ||
* @private | ||
*/ | ||
std::chrono::nanoseconds lcb_parse_golang_duration(const std::string &text); | ||
|
||
#endif // LIBCOUCHBASE_N1QL_QUERY_UTILS_HH |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters