2020-08-30 00:25:41 +02:00
|
|
|
// The Construct
|
|
|
|
//
|
|
|
|
// Copyright (C) The Construct Developers, Authors & Contributors
|
|
|
|
// Copyright (C) 2016-2020 Jason Volk <jason@zemos.net>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice is present in all copies. The
|
|
|
|
// full license for this software is available in the LICENSE file.
|
|
|
|
|
2022-05-14 22:23:09 +02:00
|
|
|
#pragma once
|
2020-08-30 00:25:41 +02:00
|
|
|
#define HAVE_IRCD_SPIRIT_GENERATE_H
|
|
|
|
|
2022-05-14 22:23:09 +02:00
|
|
|
// This file is not part of the IRCd standard include list (stdinc.h) because
|
|
|
|
// it involves extremely expensive boost headers for creating formal spirit
|
|
|
|
// grammars. This file is automatically included in the spirit.h group.
|
2020-08-30 00:25:41 +02:00
|
|
|
|
2022-07-04 23:22:18 +02:00
|
|
|
namespace ircd::spirit
|
2020-08-30 00:25:41 +02:00
|
|
|
{
|
|
|
|
IRCD_EXCEPTION(error, generator_error);
|
|
|
|
IRCD_EXCEPTION(generator_error, buffer_overrun);
|
|
|
|
|
|
|
|
using prop_mask = mpl_::int_
|
|
|
|
<
|
|
|
|
karma::generator_properties::no_properties
|
|
|
|
| karma::generator_properties::buffering
|
|
|
|
| karma::generator_properties::counting
|
|
|
|
| karma::generator_properties::tracking
|
|
|
|
| karma::generator_properties::disabling
|
|
|
|
>;
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
using sink_type = karma::detail::output_iterator
|
|
|
|
<
|
|
|
|
char *, prop_mask, unused_type
|
|
|
|
>;
|
2020-08-30 00:25:41 +02:00
|
|
|
|
2022-07-04 23:22:18 +02:00
|
|
|
constexpr size_t
|
|
|
|
generator_buffer_size {64_KiB},
|
|
|
|
generator_buffer_count {8};
|
|
|
|
|
|
|
|
extern thread_local struct generator_state *
|
|
|
|
generator_state;
|
|
|
|
|
|
|
|
extern thread_local char
|
|
|
|
generator_buffer[generator_buffer_count][generator_buffer_size];
|
|
|
|
|
2020-08-30 00:25:41 +02:00
|
|
|
template<bool truncation = false,
|
|
|
|
class gen,
|
|
|
|
class... attr>
|
2022-07-04 23:22:18 +02:00
|
|
|
[[gnu::visibility("internal"), clang::internal_linkage]]
|
2020-08-30 00:25:41 +02:00
|
|
|
bool generate(mutable_buffer &out, gen&&, attr&&...);
|
2022-07-04 23:22:18 +02:00
|
|
|
}
|
2020-08-30 00:25:41 +02:00
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
/// This structure is a shadow for the default spirit::karma buffering
|
|
|
|
/// stack. They conduct buffering by allocating and copying standard strings
|
|
|
|
/// to implement certain karma directives like "right_align[]" etc. Instead
|
|
|
|
/// we reimplement optimized buffering with assumptions specific to our
|
|
|
|
/// application's tolerances. We assume a maximum size of a buffer and maximum
|
|
|
|
/// height of any stack growing from an ircd::spirit::generate() call without
|
|
|
|
/// need for reentrancy. This gives us the ability to pre-allocate thread_local
|
|
|
|
/// buffers.
|
2022-07-04 23:22:18 +02:00
|
|
|
struct [[gnu::visibility("default")]]
|
2020-08-30 00:25:41 +02:00
|
|
|
ircd::spirit::generator_state
|
|
|
|
{
|
2020-09-04 01:47:33 +02:00
|
|
|
/// The number of instances stacked behind the current state. This should
|
|
|
|
/// never be greater than the generator_buffer_count
|
2020-08-30 00:25:41 +02:00
|
|
|
static uint depth() noexcept;
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
/// Destination buffer for this level of the stack. For depth=0 this will
|
|
|
|
/// be the user's buffer, otherwise it will be the thread-local generator
|
|
|
|
/// buffer at depth - 1.
|
|
|
|
///
|
|
|
|
/// N.B. The `begin()` of this buffer will be advanced by spirit when the
|
|
|
|
/// depth=0 as part of the output iterator process. It is never touched when
|
|
|
|
/// depth>0 because it directly refers to one of the static buffers.
|
2020-08-30 00:25:41 +02:00
|
|
|
mutable_buffer &out;
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
/// Previous in buffer stack. This is null for the instance created in the
|
|
|
|
/// frame for ircd::spirit::generate(), which is the base of the stack.
|
2020-08-30 00:25:41 +02:00
|
|
|
struct generator_state *prev {nullptr};
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
/// The number of characters we're storing in buffer at `out`. When
|
|
|
|
/// `depth>0` this will be the number of characters stored in one of the
|
|
|
|
/// static buffers between `begin(out), begin(out)+consumed` and safely
|
|
|
|
/// saturates at the maximum size of the buffer. When `depth=0` this
|
|
|
|
/// indicates the number of characters *behind* `begin(out)` it still
|
|
|
|
/// safely saturates at the maximum size of the user's buffer.
|
2020-08-30 00:25:41 +02:00
|
|
|
uint consumed {0};
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
/// The number of characters attempted, which may be larger than the buffer
|
|
|
|
/// capacity and value stored in `consumed`. Thus the difference between
|
|
|
|
/// generated and consumed is an overflow value which estimates the number
|
|
|
|
/// of characters which would've been required for the generation.
|
2020-08-30 00:25:41 +02:00
|
|
|
uint generated {0};
|
|
|
|
};
|
|
|
|
|
|
|
|
template<bool truncation,
|
|
|
|
class gen,
|
|
|
|
class... attr>
|
|
|
|
[[using gnu: always_inline, gnu_inline]]
|
|
|
|
extern inline bool
|
|
|
|
ircd::spirit::generate(mutable_buffer &out,
|
|
|
|
gen&& g,
|
|
|
|
attr&&... a)
|
|
|
|
{
|
2020-09-04 01:47:33 +02:00
|
|
|
// Save the user buffer as originally provided in case we need to restore it.
|
|
|
|
const mutable_buffer user
|
|
|
|
{
|
|
|
|
out
|
|
|
|
};
|
|
|
|
|
|
|
|
// Base instance for the buffering stack (see `struct generator_state`).
|
2020-08-30 00:25:41 +02:00
|
|
|
struct generator_state state
|
|
|
|
{
|
|
|
|
out
|
|
|
|
};
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
// Set the thread-local generator_state pointer to our stack instance. This
|
|
|
|
// will cooperatively restore whatever value was previously there, though
|
|
|
|
// it should be null.
|
|
|
|
assert(!generator_state);
|
2020-08-30 00:25:41 +02:00
|
|
|
const scope_restore _state
|
|
|
|
{
|
|
|
|
generator_state, &state
|
|
|
|
};
|
|
|
|
|
|
|
|
sink_type sink
|
|
|
|
{
|
|
|
|
begin(out)
|
|
|
|
};
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
// Enter into `spirit::karma` to execute the generator
|
2020-08-30 00:25:41 +02:00
|
|
|
const auto ret
|
|
|
|
{
|
|
|
|
karma::generate(sink, std::forward<gen>(g), std::forward<attr>(a)...)
|
|
|
|
};
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
// Determine if the user's buffer was enough to store the result; if not
|
|
|
|
// there will be an overflow value.
|
2020-08-30 00:25:41 +02:00
|
|
|
assert(state.generated >= state.consumed);
|
|
|
|
const auto overflow
|
|
|
|
{
|
|
|
|
state.generated - state.consumed
|
|
|
|
};
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
// Compile-time branch for generators that tolerate truncation (i.e. fmt::)
|
2020-08-30 00:25:41 +02:00
|
|
|
if constexpr(truncation)
|
|
|
|
{
|
|
|
|
begin(out) = begin(out) > end(out)? end(out): begin(out);
|
|
|
|
assert(begin(out) <= end(out));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
// Branch to throw an exception for generators that do not tolerate
|
|
|
|
// truncated results (i.e. json::).
|
2020-08-30 00:25:41 +02:00
|
|
|
if(unlikely(overflow || begin(out) > end(out)))
|
|
|
|
{
|
2020-09-04 01:47:33 +02:00
|
|
|
begin(out) = begin(user);
|
2020-08-30 00:25:41 +02:00
|
|
|
assert(begin(out) <= end(out));
|
|
|
|
|
|
|
|
char pbuf[2][48];
|
|
|
|
throw buffer_overrun
|
|
|
|
{
|
|
|
|
"Insufficient buffer of %s; required at least %s",
|
|
|
|
pretty(pbuf[0], iec(state.consumed)),
|
|
|
|
pretty(pbuf[1], iec(state.generated)),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-09-04 01:47:33 +02:00
|
|
|
assert(begin(out) >= end(out) - size(user));
|
2020-08-30 00:25:41 +02:00
|
|
|
assert(begin(out) <= end(out));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline uint
|
|
|
|
ircd::spirit::generator_state::depth()
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
uint ret(0);
|
|
|
|
for(auto p(ircd::spirit::generator_state); p; p = p->prev)
|
|
|
|
++ret;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// boost::spirit::karma
|
|
|
|
//
|
|
|
|
|
|
|
|
namespace boost::spirit::karma::detail
|
|
|
|
{
|
|
|
|
template<> struct enable_buffering<ircd::spirit::sink_type>;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
|
|
|
struct [[gnu::visibility("internal")]]
|
|
|
|
boost::spirit::karma::detail::enable_buffering<ircd::spirit::sink_type>
|
|
|
|
{
|
|
|
|
uint width
|
|
|
|
{
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
uint depth
|
|
|
|
{
|
|
|
|
ircd::spirit::generator_state::depth()
|
|
|
|
};
|
|
|
|
|
|
|
|
ircd::mutable_buffer buffer
|
|
|
|
{
|
|
|
|
depth?
|
|
|
|
ircd::spirit::generator_buffer[depth - 1]:
|
|
|
|
data(ircd::spirit::generator_state->out),
|
|
|
|
|
|
|
|
depth?
|
|
|
|
sizeof(ircd::spirit::generator_buffer[depth - 1]):
|
|
|
|
size(ircd::spirit::generator_state->out),
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ircd::spirit::generator_state state
|
|
|
|
{
|
|
|
|
buffer, ircd::spirit::generator_state
|
|
|
|
};
|
|
|
|
|
|
|
|
ircd::scope_restore<struct ircd::spirit::generator_state *> stack_state
|
|
|
|
{
|
|
|
|
ircd::spirit::generator_state, std::addressof(state)
|
|
|
|
};
|
|
|
|
|
|
|
|
std::size_t buffer_size() const
|
|
|
|
{
|
|
|
|
return state.generated;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename OutputIterator>
|
|
|
|
bool buffer_copy_to(OutputIterator& sink, std::size_t maxwidth = std::size_t(-1)) const = delete;
|
|
|
|
|
|
|
|
template<typename RestIterator>
|
|
|
|
bool buffer_copy_rest(RestIterator& sink, std::size_t start_at = 0) const = delete;
|
|
|
|
|
|
|
|
bool buffer_copy(std::size_t maxwidth = std::size_t(-1));
|
|
|
|
|
|
|
|
void disable();
|
|
|
|
|
|
|
|
enable_buffering(ircd::spirit::sink_type &sink, std::size_t width = std::size_t(-1));
|
|
|
|
~enable_buffering() noexcept;
|
|
|
|
};
|
|
|
|
|
|
|
|
inline
|
|
|
|
boost::spirit::karma::detail::enable_buffering<ircd::spirit::sink_type>::enable_buffering(ircd::spirit::sink_type &sink,
|
|
|
|
std::size_t width)
|
|
|
|
:width
|
|
|
|
{
|
|
|
|
uint(width)
|
|
|
|
}
|
|
|
|
{
|
|
|
|
assert(size(buffer) != 0);
|
|
|
|
assert(this->depth <= 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline
|
2022-06-20 08:09:00 +02:00
|
|
|
boost::spirit::karma::detail::enable_buffering<ircd::spirit::sink_type>::~enable_buffering()
|
2020-08-30 00:25:41 +02:00
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
assert(ircd::spirit::generator_state == &state);
|
|
|
|
//disable();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void
|
|
|
|
boost::spirit::karma::detail::enable_buffering<ircd::spirit::sink_type>::disable()
|
|
|
|
{
|
|
|
|
const auto width
|
|
|
|
{
|
|
|
|
this->width != -1U? this->width: state.consumed
|
|
|
|
};
|
|
|
|
|
|
|
|
const uint off
|
|
|
|
{
|
|
|
|
width > state.consumed? width - state.consumed: 0U
|
|
|
|
};
|
|
|
|
|
|
|
|
const ircd::const_buffer src
|
|
|
|
{
|
|
|
|
state.out, state.consumed
|
|
|
|
};
|
|
|
|
|
|
|
|
const ircd::mutable_buffer dst
|
|
|
|
{
|
|
|
|
state.out + off
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto moved
|
|
|
|
{
|
|
|
|
ircd::move(dst, src)
|
|
|
|
};
|
|
|
|
|
|
|
|
assert(moved == state.consumed);
|
|
|
|
state.consumed = !off? state.consumed: 0U;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool
|
|
|
|
boost::spirit::karma::detail::enable_buffering<ircd::spirit::sink_type>::buffer_copy(std::size_t maxwidth)
|
|
|
|
{
|
|
|
|
assert(state.prev);
|
|
|
|
auto &prev
|
|
|
|
{
|
|
|
|
*state.prev
|
|
|
|
};
|
|
|
|
|
|
|
|
const bool prev_base
|
|
|
|
{
|
|
|
|
prev.prev == nullptr
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto width
|
|
|
|
{
|
2020-08-30 06:00:15 +02:00
|
|
|
this->width != -1U?
|
|
|
|
this->width:
|
|
|
|
state.consumed
|
2020-08-30 00:25:41 +02:00
|
|
|
};
|
|
|
|
|
2020-08-30 06:00:15 +02:00
|
|
|
const auto dst_disp
|
2020-08-30 00:25:41 +02:00
|
|
|
{
|
2020-08-30 06:00:15 +02:00
|
|
|
prev_base?
|
|
|
|
-ssize_t(state.consumed):
|
|
|
|
ssize_t(prev.consumed)
|
2020-08-30 00:25:41 +02:00
|
|
|
};
|
|
|
|
|
2020-08-30 06:00:15 +02:00
|
|
|
const auto dst_over
|
2020-08-30 00:25:41 +02:00
|
|
|
{
|
2020-08-30 06:00:15 +02:00
|
|
|
std::begin(prev.out) > std::end(prev.out)?
|
|
|
|
std::distance(std::end(prev.out), std::begin(prev.out)):
|
|
|
|
0L
|
2020-08-30 00:25:41 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const auto dst_size
|
|
|
|
{
|
2020-08-30 06:00:15 +02:00
|
|
|
prev_base?
|
|
|
|
std::max(state.consumed - dst_over, 0L):
|
|
|
|
size(prev.out) - prev.consumed
|
2020-08-30 00:25:41 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const ircd::mutable_buffer dst
|
|
|
|
{
|
|
|
|
data(prev.out) + dst_disp, dst_size
|
|
|
|
};
|
|
|
|
|
2020-08-30 06:00:15 +02:00
|
|
|
const ircd::const_buffer src
|
|
|
|
{
|
|
|
|
state.out, std::max(state.consumed, width)
|
|
|
|
};
|
|
|
|
|
2020-08-30 00:25:41 +02:00
|
|
|
const auto copied
|
|
|
|
{
|
|
|
|
ircd::copy(dst, src)
|
|
|
|
};
|
|
|
|
|
|
|
|
prev.generated += state.generated;
|
|
|
|
prev.consumed += copied;
|
|
|
|
return true; // sink.good();
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
|
|
|
inline bool
|
|
|
|
boost::spirit::karma::detail::buffering_policy::output(const char &value)
|
|
|
|
{
|
|
|
|
assert(ircd::spirit::generator_state);
|
|
|
|
auto &state
|
|
|
|
{
|
|
|
|
*ircd::spirit::generator_state
|
|
|
|
};
|
|
|
|
|
|
|
|
const bool buffering
|
|
|
|
{
|
|
|
|
this->buffer != nullptr
|
|
|
|
};
|
|
|
|
|
|
|
|
const bool base
|
|
|
|
{
|
|
|
|
state.prev == nullptr
|
|
|
|
};
|
|
|
|
|
|
|
|
const uint off
|
|
|
|
{
|
|
|
|
ircd::boolmask<uint>(!base | buffering) & state.consumed
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto dst
|
|
|
|
{
|
|
|
|
state.out + off
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto copied
|
|
|
|
{
|
|
|
|
ircd::copy(dst, value)
|
|
|
|
};
|
|
|
|
|
|
|
|
state.consumed += copied;
|
|
|
|
state.generated += sizeof(char);
|
|
|
|
return !buffering;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
|
|
|
inline void
|
|
|
|
boost::spirit::karma::detail::position_policy::output(const char &value)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
template<>
|
|
|
|
template<>
|
|
|
|
inline void
|
|
|
|
boost::spirit::karma::detail::counting_policy<ircd::spirit::sink_type>::output(const char &value)
|
|
|
|
{
|
|
|
|
assert(count == nullptr);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
template<>
|
|
|
|
template<>
|
|
|
|
inline bool
|
|
|
|
boost::spirit::karma::detail::disabling_output_iterator
|
|
|
|
<
|
|
|
|
boost::spirit::karma::detail::buffering_policy,
|
|
|
|
boost::spirit::karma::detail::counting_policy<ircd::spirit::sink_type>,
|
|
|
|
boost::spirit::karma::detail::position_policy
|
|
|
|
>
|
|
|
|
::output(const char &value)
|
|
|
|
{
|
|
|
|
assert(do_output);
|
|
|
|
this->counting_policy::output(value);
|
|
|
|
this->tracking_policy::output(value);
|
|
|
|
return this->buffering_policy::output(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
|
|
|
template<>
|
|
|
|
inline void
|
|
|
|
ircd::spirit::sink_type::operator=(const char &value)
|
|
|
|
{
|
|
|
|
this->base_iterator::output(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
|
|
|
inline ircd::spirit::sink_type &
|
|
|
|
ircd::spirit::sink_type::operator++()
|
|
|
|
{
|
|
|
|
const bool has_buffer
|
|
|
|
{
|
|
|
|
this->base_iterator::has_buffer()
|
|
|
|
};
|
|
|
|
|
|
|
|
(*sink) += !has_buffer;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
|
|
|
inline ircd::spirit::sink_type
|
|
|
|
ircd::spirit::sink_type::operator++(int)
|
|
|
|
{
|
|
|
|
auto copy(*this);
|
|
|
|
++(*this);
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<>
|
|
|
|
inline bool
|
|
|
|
ircd::spirit::sink_type::good()
|
|
|
|
const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|