mirror of
https://github.com/matrix-construct/construct
synced 2024-06-02 18:18:56 +02:00
ircd::json: Introduce the json::stack device.
This commit is contained in:
parent
b528920420
commit
931fe2439e
|
@ -66,6 +66,7 @@ struct ircd::json::strung
|
|||
#include "member.h"
|
||||
#include "iov.h"
|
||||
#include "tuple/tuple.h"
|
||||
#include "stack.h"
|
||||
|
||||
namespace ircd
|
||||
{
|
||||
|
|
207
include/ircd/json/stack.h
Normal file
207
include/ircd/json/stack.h
Normal file
|
@ -0,0 +1,207 @@
|
|||
// Matrix Construct
|
||||
//
|
||||
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2018 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.
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_JSON_STACK_H
|
||||
|
||||
namespace ircd::json
|
||||
{
|
||||
struct stack;
|
||||
}
|
||||
|
||||
/// Output stack machine for stringifying JSON as-you-go. This device allows
|
||||
/// the user to create JSON without knowing the contents when it is first
|
||||
/// constructed. An object or array is opened and the user can append to the
|
||||
/// stack creating the members or values or recursing further. The JSON is
|
||||
/// then closed automatically with exception safety. Partial JSON is written
|
||||
/// to the buffer as soon as possible.
|
||||
///
|
||||
/// The target buffer is not required to maintain earlier output from the same
|
||||
/// stack or even earlier members and values of the same object or array. The
|
||||
/// buffer may be smaller than the final JSON output and reused when the user
|
||||
/// chooses to flush it to some storage or socket. If the buffer becomes full
|
||||
/// a flush callback is attempted to make space and continue. This can occur
|
||||
/// while the output is still incomplete JSON.
|
||||
///
|
||||
/// The user first creates a master json::stack instance with some reasonable
|
||||
/// backing buffer. A suite of classes is provided to aid with building the
|
||||
/// JSON which attach to each other stackfully, and eventually lead to the
|
||||
/// root. There should only be one "active path" of instances at any given
|
||||
/// time, ideally following the scope of your code itself. You must force
|
||||
/// instances to go out of scope to continue at the same recursion depth.
|
||||
/// This way the json::stack can "follow" your code and "record" the final
|
||||
/// JSON output while allowing you to free the original resources required
|
||||
/// for each value.
|
||||
///
|
||||
struct ircd::json::stack
|
||||
{
|
||||
struct array;
|
||||
struct object;
|
||||
struct member;
|
||||
using flush_callback = std::function<const_buffer (const const_buffer &)>;
|
||||
|
||||
window_buffer buf;
|
||||
flush_callback flusher;
|
||||
|
||||
object *co {nullptr}; ///< The root object instance.
|
||||
array *ca {nullptr}; ///< Could be union with top_object but
|
||||
|
||||
template<class gen, class... attr> bool printer(gen&&, attr&&...);
|
||||
template<class gen> bool printer(gen&&);
|
||||
bool append(const window_buffer::closure &);
|
||||
bool append(const string_view &);
|
||||
|
||||
public:
|
||||
bool opened() const; ///< Current stacking in progress.
|
||||
bool closed() const; ///< No stacking in progress.
|
||||
bool clean() const; ///< Never opened.
|
||||
bool done() const; ///< Opened and closed.
|
||||
|
||||
size_t remaining() const;
|
||||
const_buffer completed() const;
|
||||
void clear();
|
||||
|
||||
stack(const mutable_buffer &, flush_callback = {});
|
||||
~stack() noexcept;
|
||||
};
|
||||
|
||||
/// stack::object is constructed under the scope of either a stack::member,
|
||||
/// or a stack::array, or a stack itself. Only stack::member can be
|
||||
/// constructed directly under its scope.
|
||||
///
|
||||
/// For a stack::member parent, the named member is waiting for this value
|
||||
/// after leaving the stack at ':' after the name, this object will then
|
||||
/// print '{' and dtor with '}' and then return to the stack::member which
|
||||
/// will then return to its parent object.
|
||||
///
|
||||
/// For a stack::array parent, the stack may have been left at '[' or ','
|
||||
/// but either way this object will then print '{' and dtor with '}' and
|
||||
/// then return to the stack::array.
|
||||
///
|
||||
/// For a stack itself, this object is considered the "top object" and will
|
||||
/// open the stack with '{' and accept member instances under its scope
|
||||
/// until closing the stack with '}' after which the stack is done()
|
||||
///
|
||||
struct ircd::json::stack::object
|
||||
{
|
||||
stack *s {nullptr}; ///< root stack ref
|
||||
member *pm {nullptr}; ///< parent member (if value of one)
|
||||
array *pa {nullptr}; ///< parent array (if value in one)
|
||||
member *cm {nullptr}; ///< current child member
|
||||
size_t mc {0}; ///< members witnessed (monotonic)
|
||||
|
||||
public:
|
||||
object() = default;
|
||||
object(stack &s); ///< Object is top
|
||||
object(array &pa); ///< Object is value in the array
|
||||
object(member &pm); ///< Object is value of named member
|
||||
~object() noexcept;
|
||||
};
|
||||
|
||||
/// stack::array is constructed under the scope of either a stack::member,
|
||||
/// or a stack::array, or a stack itself. stack::object and stack::array
|
||||
/// can be constructed directly under its scope, but not stack::member.
|
||||
///
|
||||
/// The same behavior as described by stack::object documentation applies
|
||||
/// here translated to arrays.
|
||||
///
|
||||
struct ircd::json::stack::array
|
||||
{
|
||||
stack *s {nullptr}; ///< root stack ref
|
||||
member *pm {nullptr}; ///< parent member (if value of one)
|
||||
array *pa {nullptr}; ///< parent array (if value in one)
|
||||
object *co {nullptr}; ///< current child object
|
||||
array *ca {nullptr}; ///< current child array
|
||||
size_t vc {0}; ///< values witnessed (monotonic)
|
||||
|
||||
void _pre_append();
|
||||
void _post_append();
|
||||
|
||||
public:
|
||||
template<class... T> void append(const json::tuple<T...> &);
|
||||
void append(const json::value &);
|
||||
|
||||
array() = default;
|
||||
array(stack &s); ///< Array is top
|
||||
array(array &pa); ///< Array is value in the array
|
||||
array(member &pm); ///< Array is value of the named member
|
||||
~array() noexcept;
|
||||
};
|
||||
|
||||
/// stack::member is an intermediary that is constructed under the scope of
|
||||
/// a parent stack::object. It takes a name argument. It then requires one
|
||||
/// object or array be constructed under its scope as its value, or a
|
||||
/// json::value / already strung JSON must be appended as its value.
|
||||
///
|
||||
/// If the value is supplied in the constructor argument an instance of
|
||||
/// this class does not have to be held (use constructor as function).
|
||||
///
|
||||
struct ircd::json::stack::member
|
||||
{
|
||||
stack *s {nullptr}; ///< root stack ref
|
||||
object *po {nullptr}; ///< parent object
|
||||
string_view name; ///< member name state
|
||||
object *co {nullptr}; ///< current child object
|
||||
array *ca {nullptr}; ///< current child array
|
||||
|
||||
void _pre_append();
|
||||
void _post_append();
|
||||
|
||||
public:
|
||||
template<class... T> void append(const json::tuple<T...> &);
|
||||
void append(const json::value &);
|
||||
|
||||
member() = default;
|
||||
member(object &po, const string_view &name);
|
||||
member(object &po, const string_view &name, const json::value &v);
|
||||
template<class... T> member(object &po, const string_view &name, const json::tuple<T...> &t);
|
||||
~member() noexcept;
|
||||
};
|
||||
|
||||
template<class... T>
|
||||
ircd::json::stack::member::member(object &po,
|
||||
const string_view &name,
|
||||
const json::tuple<T...> &t)
|
||||
:member{po, name}
|
||||
{
|
||||
append(t);
|
||||
}
|
||||
|
||||
template<class... T>
|
||||
void
|
||||
ircd::json::stack::member::append(const json::tuple<T...> &t)
|
||||
{
|
||||
_pre_append();
|
||||
const unwind post{[this]
|
||||
{
|
||||
_post_append();
|
||||
}};
|
||||
|
||||
s->append([this, &t](mutable_buffer buf)
|
||||
{
|
||||
return size(stringify(buf, t));
|
||||
});
|
||||
}
|
||||
|
||||
template<class... T>
|
||||
void
|
||||
ircd::json::stack::array::append(const json::tuple<T...> &t)
|
||||
{
|
||||
_pre_append();
|
||||
const unwind post{[this]
|
||||
{
|
||||
_post_append();
|
||||
}};
|
||||
|
||||
s->append([&t](mutable_buffer buf)
|
||||
{
|
||||
return size(stringify(buf, t));
|
||||
});
|
||||
}
|
339
ircd/json.cc
339
ircd/json.cc
|
@ -363,6 +363,345 @@ ircd::json::input<it>::throws_exceeded()
|
|||
};
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// stack.h
|
||||
//
|
||||
|
||||
ircd::json::stack::stack(const mutable_buffer &buf,
|
||||
flush_callback flusher)
|
||||
:buf{buf}
|
||||
,flusher{std::move(flusher)}
|
||||
{
|
||||
}
|
||||
|
||||
ircd::json::stack::~stack()
|
||||
noexcept
|
||||
{
|
||||
assert(clean() || done());
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::json::stack::append(const string_view &s)
|
||||
{
|
||||
return append([&s](const mutable_buffer &buf)
|
||||
{
|
||||
return copy(buf, s);
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::json::stack::append(const window_buffer::closure &closure)
|
||||
{
|
||||
buf([&closure](const mutable_buffer &buf)
|
||||
{
|
||||
return closure(buf);
|
||||
});
|
||||
|
||||
return true; //XXX
|
||||
}
|
||||
|
||||
template<class gen,
|
||||
class... attr>
|
||||
bool
|
||||
ircd::json::stack::printer(gen&& g,
|
||||
attr&&... a)
|
||||
{
|
||||
return json::printer(buf, std::forward<gen>(g), std::forward<attr>(a)...);
|
||||
}
|
||||
|
||||
template<class gen>
|
||||
bool
|
||||
ircd::json::stack::printer(gen&& g)
|
||||
{
|
||||
return json::printer(buf, std::forward<gen>(g));
|
||||
}
|
||||
|
||||
void
|
||||
ircd::json::stack::clear()
|
||||
{
|
||||
buf.rewind(buf.consumed());
|
||||
}
|
||||
|
||||
ircd::const_buffer
|
||||
ircd::json::stack::completed()
|
||||
const
|
||||
{
|
||||
return buf.completed();
|
||||
}
|
||||
|
||||
size_t
|
||||
ircd::json::stack::remaining()
|
||||
const
|
||||
{
|
||||
return buf.remaining();
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::json::stack::done()
|
||||
const
|
||||
{
|
||||
return closed() && buf.consumed();
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::json::stack::clean()
|
||||
const
|
||||
{
|
||||
return closed() && !buf.consumed();
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::json::stack::closed()
|
||||
const
|
||||
{
|
||||
return !opened();
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::json::stack::opened()
|
||||
const
|
||||
{
|
||||
return co || ca;
|
||||
}
|
||||
|
||||
//
|
||||
// object
|
||||
//
|
||||
|
||||
ircd::json::stack::object::object(stack &s)
|
||||
:s{&s}
|
||||
{
|
||||
assert(s.clean());
|
||||
s.co = this;
|
||||
s.printer(json::printer.object_begin);
|
||||
}
|
||||
|
||||
ircd::json::stack::object::object(member &pm)
|
||||
:s{pm.s}
|
||||
,pm{&pm}
|
||||
{
|
||||
assert(s->opened());
|
||||
assert(pm.co == nullptr);
|
||||
assert(pm.ca == nullptr);
|
||||
pm.co = this;
|
||||
s->printer(json::printer.object_begin);
|
||||
}
|
||||
|
||||
ircd::json::stack::object::object(array &pa)
|
||||
:s{pa.s}
|
||||
,pa{&pa}
|
||||
{
|
||||
assert(s->opened());
|
||||
assert(pa.co == nullptr);
|
||||
assert(pa.ca == nullptr);
|
||||
pa.co = this;
|
||||
|
||||
if(pa.vc)
|
||||
s->printer(json::printer.value_sep);
|
||||
|
||||
s->printer(json::printer.object_begin);
|
||||
}
|
||||
|
||||
ircd::json::stack::object::~object()
|
||||
noexcept
|
||||
{
|
||||
assert(s);
|
||||
assert(cm == nullptr);
|
||||
s->printer(json::printer.object_end);
|
||||
|
||||
if(pm)
|
||||
{
|
||||
assert(pa == nullptr);
|
||||
assert(pm->ca == nullptr);
|
||||
assert(pm->co == this);
|
||||
pm->ca = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if(pa)
|
||||
{
|
||||
assert(pm == nullptr);
|
||||
assert(pa->co == nullptr);
|
||||
assert(pa->co == this);
|
||||
pa->vc++;
|
||||
pa->ca = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(s->co == this);
|
||||
assert(s->ca == nullptr);
|
||||
assert(pm == nullptr && pa == nullptr);
|
||||
s->co = nullptr;
|
||||
assert(s->done());
|
||||
}
|
||||
|
||||
//
|
||||
// array
|
||||
//
|
||||
|
||||
ircd::json::stack::array::array(stack &s)
|
||||
:s{&s}
|
||||
{
|
||||
assert(s.clean());
|
||||
s.ca = this;
|
||||
s.printer(json::printer.array_begin);
|
||||
}
|
||||
|
||||
ircd::json::stack::array::array(member &pm)
|
||||
:s{pm.s}
|
||||
,pm{&pm}
|
||||
{
|
||||
assert(s->opened());
|
||||
assert(pm.co == nullptr);
|
||||
assert(pm.ca == nullptr);
|
||||
pm.ca = this;
|
||||
s->printer(json::printer.array_begin);
|
||||
}
|
||||
|
||||
ircd::json::stack::array::array(array &pa)
|
||||
:s{pa.s}
|
||||
,pa{&pa}
|
||||
{
|
||||
assert(s->opened());
|
||||
assert(pa.co == nullptr);
|
||||
assert(pa.ca == nullptr);
|
||||
pa.ca = this;
|
||||
|
||||
if(pa.vc)
|
||||
s->printer(json::printer.value_sep);
|
||||
|
||||
s->printer(json::printer.array_begin);
|
||||
}
|
||||
|
||||
ircd::json::stack::array::~array()
|
||||
noexcept
|
||||
{
|
||||
assert(s);
|
||||
assert(co == nullptr);
|
||||
assert(ca == nullptr);
|
||||
s->printer(json::printer.array_end);
|
||||
|
||||
if(pm)
|
||||
{
|
||||
assert(pa == nullptr);
|
||||
assert(pm->ca == this);
|
||||
assert(pm->co == nullptr);
|
||||
pm->ca = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if(pa)
|
||||
{
|
||||
assert(pm == nullptr);
|
||||
assert(pa->ca == this);
|
||||
assert(pa->co == nullptr);
|
||||
pa->vc++;
|
||||
pa->ca = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(s->ca == this);
|
||||
assert(s->co == nullptr);
|
||||
assert(pm == nullptr && pa == nullptr);
|
||||
s->ca = nullptr;
|
||||
assert(s->done());
|
||||
}
|
||||
|
||||
void
|
||||
ircd::json::stack::array::append(const json::value &value)
|
||||
{
|
||||
assert(s);
|
||||
_pre_append();
|
||||
const unwind post{[this]
|
||||
{
|
||||
_post_append();
|
||||
}};
|
||||
|
||||
s->append([&value](mutable_buffer buf)
|
||||
{
|
||||
return size(stringify(buf, value));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ircd::json::stack::array::_pre_append()
|
||||
{
|
||||
if(vc)
|
||||
s->printer(json::printer.value_sep);
|
||||
}
|
||||
|
||||
void
|
||||
ircd::json::stack::array::_post_append()
|
||||
{
|
||||
++vc;
|
||||
}
|
||||
|
||||
//
|
||||
// member
|
||||
//
|
||||
|
||||
ircd::json::stack::member::member(object &po,
|
||||
const string_view &name)
|
||||
:s{po.s}
|
||||
,po{&po}
|
||||
,name{name}
|
||||
{
|
||||
assert(po.cm == nullptr);
|
||||
po.cm = this;
|
||||
|
||||
if(po.mc)
|
||||
s->printer(json::printer.value_sep);
|
||||
|
||||
s->printer(json::printer.name << json::printer.name_sep, name);
|
||||
}
|
||||
|
||||
ircd::json::stack::member::member(object &po,
|
||||
const string_view &name,
|
||||
const json::value &value)
|
||||
:member{po, name}
|
||||
{
|
||||
append(value);
|
||||
}
|
||||
|
||||
ircd::json::stack::member::~member()
|
||||
noexcept
|
||||
{
|
||||
assert(s);
|
||||
assert(co == nullptr);
|
||||
assert(ca == nullptr);
|
||||
assert(po);
|
||||
assert(po->cm == this);
|
||||
po->mc++;
|
||||
po->cm = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
ircd::json::stack::member::append(const json::value &value)
|
||||
{
|
||||
assert(s);
|
||||
_pre_append();
|
||||
const unwind post{[this]
|
||||
{
|
||||
_post_append();
|
||||
}};
|
||||
|
||||
s->append([&value](mutable_buffer buf)
|
||||
{
|
||||
return size(stringify(buf, value));
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ircd::json::stack::member::_pre_append()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ircd::json::stack::member::_post_append()
|
||||
{
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// iov.h
|
||||
|
|
Loading…
Reference in a new issue