150 lines
5.1 KiB
C++
150 lines
5.1 KiB
C++
/*
|
|
* 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
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
|
|
*/
|
|
|
|
#include "parse/time.hpp"
|
|
|
|
#include "parse/value.hpp"
|
|
#include "../ascii.hpp"
|
|
|
|
#include <chrono>
|
|
|
|
|
|
// CXX#20: Define these ratios that aren't currently in all our
|
|
// implementations of the stdlib.
|
|
using days = std::chrono::duration<std::int_fast32_t, std::ratio<86400>>;
|
|
using weeks = std::chrono::duration<std::int_fast32_t, std::ratio<604800>>;
|
|
using months = std::chrono::duration<std::int_fast32_t, std::ratio<2629746>>;
|
|
using years = std::chrono::duration<std::int_fast32_t, std::ratio<31556952>>;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/// Shorthand for converting an arbitrary duration to nanoseconds.
|
|
template <typename DurationT>
|
|
std::chrono::nanoseconds to_ns (DurationT val)
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds> (val);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
enum plural_t {
|
|
PLURAL, SINGULAR
|
|
};
|
|
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Try to consume a prefix of a string view, optionally allowing for
|
|
/// pluralisation.
|
|
///
|
|
/// If the prefix is detected the provided view is modified to point
|
|
/// immediately past the prefix, and the value true is returned. Else, the view
|
|
/// is untouched and false is returned.
|
|
///
|
|
/// \tparam _N The number of characters in the null-terminated prefix.
|
|
/// \tparam N The number of displayable characters in the prefix.
|
|
/// \param str The view to be checked for the prefix.
|
|
/// \param prefix The prefix we are checking for.
|
|
/// \param plural Whether to (optionall) perform pluralisation on the prefix.
|
|
/// \return True IFF the prefix was found and the view was updated.
|
|
template <std::size_t N_, std::size_t N = N_ - 1>
|
|
static bool
|
|
try_consume_prefix (
|
|
cruft::view<char const*> &str,
|
|
char const (&prefix)[N_],
|
|
plural_t plural = SINGULAR)
|
|
{
|
|
static_assert (N > 0);
|
|
|
|
// Ensure both sequences have the same prefix
|
|
if (str.size () < N)
|
|
return false;
|
|
|
|
for (size_t i = 0; i < N; ++i)
|
|
if (str[i] != prefix[i])
|
|
return false;
|
|
|
|
|
|
// Eat the prefix into a temporary
|
|
auto res = str.consume (N);
|
|
|
|
// Each the plural suffix if we support one
|
|
if (plural == PLURAL && res.size () >= 1 && res[0] == 's')
|
|
res = res.consume (1);
|
|
|
|
// If, after the above, we are empty or we have space then we have been
|
|
// successful...
|
|
if (res.empty () || cruft::ascii::is_space (str[0])) {
|
|
str = res;
|
|
return true;
|
|
}
|
|
|
|
// ... otherwise we probably have a trailing unprocessed suffix;
|
|
// eg, 's' with a trailing 'econds'
|
|
return false;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
cruft::expected<std::chrono::nanoseconds, std::errc>
|
|
cruft::parse::duration::consume (cruft::view<char const*> &remain)
|
|
{
|
|
auto const count = value<std::intmax_t> (remain);
|
|
if (remain.empty ())
|
|
return to_ns (std::chrono::seconds (count));
|
|
|
|
while (remain[0] == ' ') {
|
|
remain = remain.consume (1);
|
|
if (remain.empty ())
|
|
return to_ns (std::chrono::seconds (count));
|
|
}
|
|
|
|
if (try_consume_prefix (remain, "ns")) return std::chrono::nanoseconds (count);
|
|
if (try_consume_prefix (remain, "us")) return to_ns (std::chrono::microseconds (count));
|
|
if (try_consume_prefix (remain, "ms")) return to_ns (std::chrono::milliseconds (count));
|
|
if (try_consume_prefix (remain, "s")) return to_ns (std::chrono::seconds (count));
|
|
if (try_consume_prefix (remain, "m")) return to_ns (std::chrono::minutes (count));
|
|
if (try_consume_prefix (remain, "h")) return to_ns (std::chrono::hours (count));
|
|
|
|
if (try_consume_prefix (remain, "second", PLURAL))
|
|
return to_ns (std::chrono::seconds (count));
|
|
if (try_consume_prefix (remain, "minute", PLURAL))
|
|
return to_ns (std::chrono::minutes (count));
|
|
if (try_consume_prefix (remain, "hour", PLURAL))
|
|
return to_ns (std::chrono::hours (count));
|
|
|
|
if (try_consume_prefix (remain, "day", PLURAL))
|
|
return to_ns (days (count));
|
|
if (try_consume_prefix (remain, "week", PLURAL))
|
|
return to_ns (weeks (count));
|
|
if (try_consume_prefix (remain, "month", PLURAL))
|
|
return to_ns (months (count));
|
|
if (try_consume_prefix (remain, "year", PLURAL))
|
|
return to_ns (years (count));
|
|
|
|
return cruft::unexpected (std::errc::invalid_argument);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
cruft::expected<std::chrono::nanoseconds, std::errc>
|
|
cruft::parse::duration::from (cruft::view<char const*> const &str)
|
|
{
|
|
auto remain = str;
|
|
|
|
// Try to parse from the temporary view.
|
|
auto res = ::cruft::parse::duration::consume (remain);
|
|
if (!res)
|
|
return res;
|
|
|
|
// Ensure it was totally consumed.
|
|
if (!remain.empty ())
|
|
return cruft::unexpected (std::errc::invalid_argument);
|
|
|
|
return res;
|
|
}
|