diff options
author | Josh Haberman <jhaberman@gmail.com> | 2013-12-20 17:40:40 -0800 |
---|---|---|
committer | Josh Haberman <jhaberman@gmail.com> | 2013-12-20 17:40:40 -0800 |
commit | ce9bba3cb5409844f8f3d7dcc235a9ea30cad090 (patch) | |
tree | 6c4e0a7c023c790a278f3616c749280c8da205af /upb/sink.h | |
parent | aa8db6ab5ea18848247b8c4ac4715cf344941e94 (diff) |
Sync from Google-internal development.
Diffstat (limited to 'upb/sink.h')
-rw-r--r-- | upb/sink.h | 535 |
1 files changed, 318 insertions, 217 deletions
@@ -24,144 +24,64 @@ #ifdef __cplusplus namespace upb { -class Pipeline; +class BufferSource; +class BytesSink; class Sink; -template <int size> class SeededPipeline; } -typedef upb::Pipeline upb_pipeline; +typedef upb::BufferSource upb_bufsrc; +typedef upb::BytesSink upb_bytessink; typedef upb::Sink upb_sink; #else -struct upb_pipeline; struct upb_sink; -typedef struct upb_pipeline upb_pipeline; +struct upb_bufsrc; +struct upb_bytessink; typedef struct upb_sink upb_sink; +typedef struct upb_bytessink upb_bytessink; +typedef struct upb_bufsrc upb_bufsrc; #endif -struct upb_sinkframe; -// The maximum nesting depth that upb::Sink will allow. Matches proto2's limit. -// TODO: make this a runtime-settable property of Sink. -#define UPB_SINK_MAX_NESTING 64 - -#ifdef __cplusplus +// Internal-only struct for the sink. +struct upb_sinkframe { + const upb_handlers *h; + void *closure; -// A upb::Pipeline is a set of sinks that can send data to each other. The -// pipeline object also contains an arena allocator that the sinks and their -// associated processing state can use for fast memory allocation. This makes -// pipelines very fast to construct and destroy, especially if the arena is -// supplied with an initial block of memory. If this initial block of memory -// is from the C stack and is large enough, then actual heap allocation can be -// avoided entirely which significantly reduces overhead in some cases. -// -// All sinks and processing state are automatically freed when the pipeline is -// destroyed, so Free() is not necessary or possible. Allocated objects can -// optionally specify a Reset() callback that will be called when whenever the -// pipeline is Reset() or destroyed. This can be used to free any outside -// resources the object is holding. -// -// Pipelines (and sinks/objects allocated from them) are not thread-safe! -class upb::Pipeline { - public: - // Initializes the pipeline's arena with the given initial memory that will - // be used before allocating memory using the given allocation function. - // The "ud" pointer will be passed as the first parameter to the realloc - // callback, and can be used to pass user-specific state. - Pipeline(void *initial_mem, size_t initial_size, - void *(*realloc)(void *ud, void *ptr, size_t size), void *ud); - ~Pipeline(); - - // Returns a newly-allocated Sink for the given handlers. The sink is will - // live as long as the pipeline does. Caller retains ownership of the - // handlers object, which must outlive the pipeline. + // For any frames besides the top, this is the END* callback that will run + // when the subframe is popped (for example, for a "sequence" frame the frame + // above it will be a UPB_HANDLER_ENDSEQ handler). But this is only + // necessary for assertion checking inside upb_sink and can be omitted if the + // sink has only one caller. // - // TODO(haberman): add an option for the sink to take a ref, so the handlers - // don't have to outlive? This would be simpler but imposes a minimum cost. - // Taking an atomic ref is not *so* bad in the single-threaded case, but this - // can degrade heavily under contention, so we need a way to avoid it in - // cases where this overhead would be significant and the caller can easily - // guarantee the outlive semantics. - Sink* NewSink(const Handlers* handlers); - - // Accepts a ref donated from the given owner. Will unref the Handlers when - // the Pipeline is destroyed. - void DonateRef(const Handlers* h, const void* owner); - - // The current error status for the pipeline. - const upb::Status& status() const; - - // Calls "reset" on all Sinks and resettable state objects in the arena, and - // resets the error status. Useful for resetting processing state so new - // input can be accepted. - void Reset(); - - // Allocates/reallocates memory of the given size, or returns NULL if no - // memory is available. It is not necessary (or possible) to manually free - // the memory obtained from these functions. - void* Alloc(size_t size); - void* Realloc(void* ptr, size_t old_size, size_t size); - - // Allocates an object with the given FrameType. Note that this object may - // *not* be resized with Realloc(). - void* AllocObject(const FrameType* type); - - private: -#else -struct upb_pipeline { -#endif - void *(*realloc)(void *ud, void *ptr, size_t size); - void *ud; - void *bump_top; // Current alloc offset, either from initial or dyn region. - void *bump_limit; // Limit of current alloc block. - void *obj_head; // Linked list of objects with "reset" functions. - void *region_head; // Linked list of dyn regions we got from user's realloc(). - void *last_alloc; - upb_status status_; + // TODO(haberman): have a mechanism for ensuring that a sink only has one + // caller. + upb_selector_t selector; }; -struct upb_frametype { - size_t size; - void (*init)(void* obj, upb_pipeline *p); - void (*uninit)(void* obj); - void (*reset)(void* obj); -}; +// The maximum nesting depth that upb::Sink will allow. Matches proto2's limit. +// TODO: make this a runtime-settable property of Sink. +#define UPB_SINK_MAX_NESTING 64 #ifdef __cplusplus -// For convenience, a template for a pipeline with an array of initial memory. -template <int initial_size> -class upb::SeededPipeline : public upb::Pipeline { - public: - SeededPipeline(void *(*realloc)(void *ud, void *ptr, size_t size), void *ud) - : Pipeline(mem_, initial_size, realloc, ud) { - } - - private: - char mem_[initial_size]; -}; - // A upb::Sink is an object that binds a upb::Handlers object to some runtime -// state. It is the object that can actually call a set of handlers. -// -// Unlike upb::Def and upb::Handlers, upb::Sink is never frozen, immutable, or -// thread-safe. You can create as many of them as you want, but each one may -// only be used in a single thread at a time. -// -// If we compare with class-based OOP, a you can think of a upb::Def as an -// abstract base class, a upb::Handlers as a concrete derived class, and a -// upb::Sink as an object (class instance). -// -// Each upb::Sink lives in exactly one pipeline. +// state. It represents an endpoint to which data can be sent. class upb::Sink { public: + // Constructor with no initialization; must be Reset() before use. + Sink() {} - // Resets the state of the sink so that it is ready to accept new input. - // Any state from previously received data is discarded. "Closure" will be - // used as the top-level closure. - void Reset(void *closure); + // Constructs a new sink for the given frozen handlers and closure. + // + // TODO: once the Handlers know the expected closure type, verify that T + // matches it. + template <class T> Sink(const Handlers* handlers, T* closure); - // Returns the pipeline that this sink comes from. - Pipeline* pipeline() const; + // Resets the value of the sink. + template <class T> void Reset(const Handlers* handlers, T* closure); // Returns the top-level object that is bound to this sink. + // + // TODO: once the Handlers know the expected closure type, verify that T + // matches it. template <class T> T* GetObject() const; // Functions for pushing data into the sink. @@ -178,10 +98,10 @@ class upb::Sink { // sink->StartSubMessage(startsubmsg_selector); // sink->StartMessage(); // // ... - // sink->EndMessage(); + // sink->EndMessage(&status); // sink->EndSubMessage(endsubmsg_selector); bool StartMessage(); - bool EndMessage(); + bool EndMessage(Status* status); // Putting of individual values. These work for both repeated and // non-repeated fields, but for repeated fields you must wrap them in @@ -196,122 +116,306 @@ class upb::Sink { // Putting of string/bytes values. Each string can consist of zero or more // non-contiguous buffers of data. - bool StartString(Handlers::Selector s, size_t size_hint); + // + // For StartString(), the function will write a sink for the string to "sub." + // The sub-sink must be used for any/all PutStringBuffer() calls. + bool StartString(Handlers::Selector s, size_t size_hint, Sink* sub); size_t PutStringBuffer(Handlers::Selector s, const char *buf, size_t len); bool EndString(Handlers::Selector s); // For submessage fields. - bool StartSubMessage(Handlers::Selector s); + // + // For StartSubMessage(), the function will write a sink for the string to + // "sub." The sub-sink must be used for any/all handlers called within the + // submessage. + bool StartSubMessage(Handlers::Selector s, Sink* sub); bool EndSubMessage(Handlers::Selector s); // For repeated fields of any type, the sequence of values must be wrapped in // these calls. - bool StartSequence(Handlers::Selector s); + // + // For StartSequence(), the function will write a sink for the string to + // "sub." The sub-sink must be used for any/all handlers called within the + // sequence. + bool StartSequence(Handlers::Selector s, Sink* sub); bool EndSequence(Handlers::Selector s); - private: - UPB_DISALLOW_POD_OPS(Sink); + // Copy and assign specifically allowed. + // We don't even bother making these members private because so many + // functions need them and this is mainly just a dumb data container anyway. #else struct upb_sink { #endif - upb_pipeline *pipeline_; - struct upb_sinkframe *top, *limit, *stack; + const upb_handlers *handlers; + void *closure; }; #ifdef __cplusplus -extern "C" { -#endif -void *upb_realloc(void *ud, void *ptr, size_t size); -void upb_pipeline_init(upb_pipeline *p, void *initial_mem, size_t initial_size, - void *(*realloc)(void *ud, void *ptr, size_t size), - void *ud); -void upb_pipeline_uninit(upb_pipeline *p); -void *upb_pipeline_alloc(upb_pipeline *p, size_t size); -void *upb_pipeline_realloc( - upb_pipeline *p, void *ptr, size_t old_size, size_t size); -void *upb_pipeline_allocobj(upb_pipeline *p, const upb_frametype *type); -void upb_pipeline_reset(upb_pipeline *p); -void upb_pipeline_donateref( - upb_pipeline *p, const upb_handlers *h, const void *owner); -upb_sink *upb_pipeline_newsink(upb_pipeline *p, const upb_handlers *h); -const upb_status *upb_pipeline_status(const upb_pipeline *p); - -void upb_sink_reset(upb_sink *s, void *closure); -upb_pipeline *upb_sink_pipeline(const upb_sink *s); -void *upb_sink_getobj(const upb_sink *s); -bool upb_sink_startmsg(upb_sink *s); -bool upb_sink_endmsg(upb_sink *s); -bool upb_sink_putint32(upb_sink *s, upb_selector_t sel, int32_t val); -bool upb_sink_putint64(upb_sink *s, upb_selector_t sel, int64_t val); -bool upb_sink_putuint32(upb_sink *s, upb_selector_t sel, uint32_t val); -bool upb_sink_putuint64(upb_sink *s, upb_selector_t sel, uint64_t val); -bool upb_sink_putfloat(upb_sink *s, upb_selector_t sel, float val); -bool upb_sink_putdouble(upb_sink *s, upb_selector_t sel, double val); -bool upb_sink_putbool(upb_sink *s, upb_selector_t sel, bool val); -bool upb_sink_startstr(upb_sink *s, upb_selector_t sel, size_t size_hint); -size_t upb_sink_putstring(upb_sink *s, upb_selector_t sel, const char *buf, - size_t len); -bool upb_sink_endstr(upb_sink *s, upb_selector_t sel); -bool upb_sink_startsubmsg(upb_sink *s, upb_selector_t sel); -bool upb_sink_endsubmsg(upb_sink *s, upb_selector_t sel); -bool upb_sink_startseq(upb_sink *s, upb_selector_t sel); -bool upb_sink_endseq(upb_sink *s, upb_selector_t sel); +class upb::BytesSink { + public: + BytesSink() {} + + // Constructs a new sink for the given frozen handlers and closure. + // + // TODO(haberman): once the Handlers know the expected closure type, verify + // that T matches it. + template <class T> BytesSink(const Handlers* handlers, T* closure); + + // Resets the value of the sink. + template <class T> void Reset(const Handlers* handlers, T* closure); + + bool Start(size_t size_hint, void **subc); + size_t PutBuffer(void *subc, const char *buf, size_t len); + bool End(); + +#else +struct upb_bytessink { +#endif + const upb_byteshandler *handler; + void *closure; +}; #ifdef __cplusplus -} /* extern "C" */ + +// A class for pushing a flat buffer of data to a BytesSink. +// You can construct an instance of this to get a resumable source, +// or just call the static PutBuffer() to do a non-resumable push all in one go. +class upb::BufferSource { + public: + BufferSource(); + BufferSource(const char* buf, size_t len, BytesSink* sink); + + // Returns true if the entire buffer was pushed successfully. Otherwise the + // next call to PutNext() will resume where the previous one left off. + // TODO(haberman): implement this. + bool PutNext(); + + // A static version; with this version is it not possible to resume in the + // case of failure or a partially-consumed buffer. + static bool PutBuffer(const char* buf, size_t len, BytesSink* sink); + + template <class T> static bool PutBuffer(const T& str, BytesSink* sink) { + return PutBuffer(str.c_str(), str.size(), sink); + } + + private: +#else +struct upb_bufsrc { #endif +}; #ifdef __cplusplus +extern "C" { +#endif -namespace upb { +// Inline definitions. + +UPB_INLINE void upb_bytessink_reset(upb_bytessink *s, const upb_byteshandler *h, + void *closure) { + s->handler = h; + s->closure = closure; +} + +UPB_INLINE bool upb_bytessink_start(upb_bytessink *s, size_t size_hint, + void **subc) { + if (!s->handler) return true; + upb_startstr_handlerfunc *start = + (upb_startstr_handlerfunc *)s->handler->table[UPB_STARTSTR_SELECTOR].func; + + if (!start) return true; + *subc = start(s->closure, upb_handlerattr_handlerdata( + &s->handler->table[UPB_STARTSTR_SELECTOR].attr), + size_hint); + return *subc != NULL; +} + +UPB_INLINE size_t upb_bytessink_putbuf(upb_bytessink *s, void *subc, + const char *buf, size_t size) { + if (!s->handler) return true; + upb_string_handlerfunc *putbuf = + (upb_string_handlerfunc *)s->handler->table[UPB_STRING_SELECTOR].func; + + if (!putbuf) return true; + return putbuf(subc, upb_handlerattr_handlerdata( + &s->handler->table[UPB_STRING_SELECTOR].attr), + buf, size); +} + +UPB_INLINE bool upb_bytessink_end(upb_bytessink *s) { + if (!s->handler) return true; + upb_endfield_handlerfunc *end = + (upb_endfield_handlerfunc *)s->handler->table[UPB_ENDSTR_SELECTOR].func; + + if (!end) return true; + return end(s->closure, + upb_handlerattr_handlerdata( + &s->handler->table[UPB_ENDSTR_SELECTOR].attr)); +} + +UPB_INLINE bool upb_bufsrc_putbuf(const char *buf, size_t len, + upb_bytessink *sink) { + void *subc; + return + upb_bytessink_start(sink, len, &subc) && + (len == 0 || upb_bytessink_putbuf(sink, subc, buf, len) == len) && + upb_bytessink_end(sink); +} +#define PUTVAL(type, ctype) \ + UPB_INLINE bool upb_sink_put##type(upb_sink *s, upb_selector_t sel, \ + ctype val) { \ + if (!s->handlers) return true; \ + upb_##type##_handlerfunc *func = \ + (upb_##type##_handlerfunc *)upb_handlers_gethandler(s->handlers, sel); \ + if (!func) return true; \ + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); \ + return func(s->closure, hd, val); \ + } -inline Pipeline::Pipeline(void *initial_mem, size_t initial_size, - void *(*realloc)(void *ud, void *ptr, size_t size), - void *ud) { - upb_pipeline_init(this, initial_mem, initial_size, realloc, ud); +PUTVAL(int32, int32_t); +PUTVAL(int64, int64_t); +PUTVAL(uint32, uint32_t); +PUTVAL(uint64, uint64_t); +PUTVAL(float, float); +PUTVAL(double, double); +PUTVAL(bool, bool); +#undef PUTVAL + +UPB_INLINE void upb_sink_reset(upb_sink *s, const upb_handlers *h, void *c) { + s->handlers = h; + s->closure = c; } -inline Pipeline::~Pipeline() { - upb_pipeline_uninit(this); + +UPB_INLINE size_t +upb_sink_putstring(upb_sink *s, upb_selector_t sel, const char *buf, size_t n) { + if (!s->handlers) return n; + upb_string_handlerfunc *handler = + (upb_string_handlerfunc *)upb_handlers_gethandler(s->handlers, sel); + + if (!handler) return n; + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); + return handler(s->closure, hd, buf, n); } -inline void* Pipeline::Alloc(size_t size) { - return upb_pipeline_alloc(this, size); + +UPB_INLINE bool upb_sink_startmsg(upb_sink *s) { + if (!s->handlers) return true; + upb_startmsg_handlerfunc *startmsg = + (upb_startmsg_handlerfunc *)upb_handlers_gethandler(s->handlers, + UPB_STARTMSG_SELECTOR); + if (!startmsg) return true; + const void *hd = + upb_handlers_gethandlerdata(s->handlers, UPB_STARTMSG_SELECTOR); + return startmsg(s->closure, hd); } -inline void* Pipeline::Realloc(void* ptr, size_t old_size, size_t size) { - return upb_pipeline_realloc(this, ptr, old_size, size); + +UPB_INLINE bool upb_sink_endmsg(upb_sink *s, upb_status *status) { + if (!s->handlers) return true; + upb_endmsg_handlerfunc *endmsg = + (upb_endmsg_handlerfunc *)upb_handlers_gethandler(s->handlers, + UPB_ENDMSG_SELECTOR); + + if (!endmsg) return true; + const void *hd = + upb_handlers_gethandlerdata(s->handlers, UPB_ENDMSG_SELECTOR); + return endmsg(s->closure, hd, status); } -inline void* Pipeline::AllocObject(const upb::FrameType* type) { - return upb_pipeline_allocobj(this, type); + +UPB_INLINE bool upb_sink_startseq(upb_sink *s, upb_selector_t sel, + upb_sink *sub) { + sub->closure = s->closure; + sub->handlers = s->handlers; + if (!s->handlers) return true; + upb_startfield_handlerfunc *startseq = + (upb_startfield_handlerfunc*)upb_handlers_gethandler(s->handlers, sel); + + if (!startseq) return true; + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); + sub->closure = startseq(s->closure, hd); + return sub->closure ? true : false; } -inline void Pipeline::Reset() { - upb_pipeline_reset(this); + +UPB_INLINE bool upb_sink_endseq(upb_sink *s, upb_selector_t sel) { + if (!s->handlers) return true; + upb_endfield_handlerfunc *endseq = + (upb_endfield_handlerfunc*)upb_handlers_gethandler(s->handlers, sel); + + if (!endseq) return true; + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); + return endseq(s->closure, hd); } -inline const upb::Status& Pipeline::status() const { - return *upb_pipeline_status(this); + +UPB_INLINE bool upb_sink_startstr(upb_sink *s, upb_selector_t sel, + size_t size_hint, upb_sink *sub) { + sub->closure = s->closure; + sub->handlers = s->handlers; + if (!s->handlers) return true; + upb_startstr_handlerfunc *startstr = + (upb_startstr_handlerfunc*)upb_handlers_gethandler(s->handlers, sel); + + if (!startstr) return true; + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); + sub->closure = startstr(s->closure, hd, size_hint); + return sub->closure ? true : false; } -inline Sink* Pipeline::NewSink(const upb::Handlers* handlers) { - return upb_pipeline_newsink(this, handlers); + +UPB_INLINE bool upb_sink_endstr(upb_sink *s, upb_selector_t sel) { + if (!s->handlers) return true; + upb_endfield_handlerfunc *endstr = + (upb_endfield_handlerfunc*)upb_handlers_gethandler(s->handlers, sel); + + if (!endstr) return true; + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); + return endstr(s->closure, hd); } -inline void Pipeline::DonateRef(const upb::Handlers* h, const void *owner) { - return upb_pipeline_donateref(this, h, owner); + +UPB_INLINE bool upb_sink_startsubmsg(upb_sink *s, upb_selector_t sel, + upb_sink *sub) { + sub->closure = s->closure; + if (!s->handlers) { + sub->handlers = NULL; + return true; + } + sub->handlers = upb_handlers_getsubhandlers_sel(s->handlers, sel); + upb_startfield_handlerfunc *startsubmsg = + (upb_startfield_handlerfunc*)upb_handlers_gethandler(s->handlers, sel); + + if (!startsubmsg) return true; + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); + sub->closure = startsubmsg(s->closure, hd); + return sub->closure ? true : false; } -inline void Sink::Reset(void *closure) { - upb_sink_reset(this, closure); +UPB_INLINE bool upb_sink_endsubmsg(upb_sink *s, upb_selector_t sel) { + if (!s->handlers) return true; + upb_endfield_handlerfunc *endsubmsg = + (upb_endfield_handlerfunc*)upb_handlers_gethandler(s->handlers, sel); + + if (!endsubmsg) return s->closure; + const void *hd = upb_handlers_gethandlerdata(s->handlers, sel); + return endsubmsg(s->closure, hd); } -inline Pipeline* Sink::pipeline() const { - return upb_sink_pipeline(this); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#ifdef __cplusplus + +namespace upb { + +template <class T> Sink::Sink(const Handlers* handlers, T* closure) { + upb_sink_reset(this, handlers, closure); } template <class T> -inline T* Sink::GetObject() const { - return static_cast<T*>(upb_sink_getobj(this)); +inline void Sink::Reset(const Handlers* handlers, T* closure) { + upb_sink_reset(this, handlers, closure); } inline bool Sink::StartMessage() { return upb_sink_startmsg(this); } -inline bool Sink::EndMessage() { - return upb_sink_endmsg(this); +inline bool Sink::EndMessage(Status* status) { + return upb_sink_endmsg(this, status); } inline bool Sink::PutInt32(Handlers::Selector sel, int32_t val) { return upb_sink_putint32(this, sel, val); @@ -334,8 +438,9 @@ inline bool Sink::PutDouble(Handlers::Selector sel, double val) { inline bool Sink::PutBool(Handlers::Selector sel, bool val) { return upb_sink_putbool(this, sel, val); } -inline bool Sink::StartString(Handlers::Selector sel, size_t size_hint) { - return upb_sink_startstr(this, sel, size_hint); +inline bool Sink::StartString(Handlers::Selector sel, size_t size_hint, + Sink *sub) { + return upb_sink_startstr(this, sel, size_hint, sub); } inline size_t Sink::PutStringBuffer(Handlers::Selector sel, const char *buf, size_t len) { @@ -344,39 +449,35 @@ inline size_t Sink::PutStringBuffer(Handlers::Selector sel, const char *buf, inline bool Sink::EndString(Handlers::Selector sel) { return upb_sink_endstr(this, sel); } -inline bool Sink::StartSubMessage(Handlers::Selector sel) { - return upb_sink_startsubmsg(this, sel); +inline bool Sink::StartSubMessage(Handlers::Selector sel, Sink* sub) { + return upb_sink_startsubmsg(this, sel, sub); } inline bool Sink::EndSubMessage(Handlers::Selector sel) { return upb_sink_endsubmsg(this, sel); } -inline bool Sink::StartSequence(Handlers::Selector sel) { - return upb_sink_startseq(this, sel); +inline bool Sink::StartSequence(Handlers::Selector sel, Sink* sub) { + return upb_sink_startseq(this, sel, sub); } inline bool Sink::EndSequence(Handlers::Selector sel) { return upb_sink_endseq(this, sel); } -} // namespace upb -#endif +inline bool BytesSink::Start(size_t size_hint, void **subc) { + return upb_bytessink_start(this, size_hint, subc); +} +inline size_t BytesSink::PutBuffer(void *subc, const char *buf, size_t len) { + return upb_bytessink_putbuf(this, subc, buf, len); +} +inline bool BytesSink::End() { + return upb_bytessink_end(this); +} -// TODO(haberman): move this to sink.c. We keep it here now only because the -// JIT needs to modify it directly, which it only needs to do because it makes -// the interpreter handle fallback cases. When the JIT is self-sufficient, it -// will no longer need to touch the sink's stack at all. -struct upb_sinkframe { - const upb_handlers *h; - void *closure; +inline bool BufferSource::PutBuffer(const char *buf, size_t len, + BytesSink *sink) { + return upb_bufsrc_putbuf(buf, len, sink); +} - // For any frames besides the top, this is the END* callback that will run - // when the subframe is popped (for example, for a "sequence" frame the frame - // above it will be a UPB_HANDLER_ENDSEQ handler). But this is only - // necessary for assertion checking inside upb_sink and can be omitted if the - // sink has only one caller. - // - // TODO(haberman): have a mechanism for ensuring that a sink only has one - // caller. - upb_selector_t selector; -}; +} // namespace upb +#endif #endif |