1+ #pragma once
2+
3+ /*
4+ * mruby-cbor C++ API
5+ *
6+ * Provides a zero-overhead C++ wrapper over the mruby-cbor C API.
7+ * The Lazy proxy type mirrors simdjson's OnDemand design: operator[] chains
8+ * without raising immediately on a miss; the error is carried in the proxy
9+ * and only raised when .value() is called.
10+ *
11+ * Usage:
12+ *
13+ * #include <mruby/cbor.hpp>
14+ *
15+ * // Encode
16+ * mrb_value buf = CBOR::encode(mrb, obj);
17+ * mrb_value buf = CBOR::encode(mrb, obj, true); // sharedrefs
18+ *
19+ * // Eager decode
20+ * mrb_value obj = CBOR::decode(mrb, buf);
21+ *
22+ * // Lazy decode — operator[] chains, .value() materializes
23+ * auto doc = CBOR::decode_lazy(mrb, buf);
24+ * mrb_value text = doc["statuses"][0]["text"].value();
25+ *
26+ * // Operator bool checks for error/nil before materializing
27+ * auto field = doc["maybe"]["missing"];
28+ * if (field) {
29+ * mrb_value v = field.value();
30+ * }
31+ *
32+ * // doc_end for streaming
33+ * mrb_value end_offset = CBOR::doc_end(mrb, buf, 0);
34+ *
35+ * // Register a tag
36+ * CBOR::register_tag(mrb, 1000, my_class);
37+ */
38+
39+ #include " mruby/num_helpers.hpp"
40+ #include < mruby.h>
41+ #include < mruby/class.h>
42+ #include < mruby/error.h>
43+ #include < mruby/presym.h>
44+ #include < mruby/string.h>
45+ #include < mruby/cbor.h>
46+ #include < string_view>
47+ #include < mruby/cpp_to_mrb_value.hpp>
48+
49+ namespace CBOR {
50+
51+ // ============================================================================
52+ // Lazy — simdjson-style result-carrying proxy
53+ //
54+ // Each operator[] returns a new Lazy. If the underlying mrb call raises,
55+ // the exception is cleared and the error is stored in the proxy. The error
56+ // is re-raised lazily when .value() is called.
57+ //
58+ // operator bool returns false if the proxy carries an error or wraps nil.
59+ // ============================================================================
60+
61+ class Lazy {
62+ public:
63+ mrb_state *mrb;
64+
65+ // Construct from a live handle (normal path)
66+ Lazy (mrb_state *mrb, mrb_value handle)
67+ : mrb(mrb), handle_(handle), has_error_(false ), exc_(mrb_undef_value()) {}
68+
69+ // Construct carrying a pending error (miss/error propagation path)
70+ Lazy (mrb_state *mrb, mrb_value exc, bool /* error_tag*/ )
71+ : mrb(mrb), handle_(mrb_undef_value()), has_error_(true ), exc_(exc) {}
72+
73+ template <size_t N>
74+ Lazy operator [](const char (&key)[N]) const {
75+ return aref (mrb_str_new_static (mrb, key, N - 1 ));
76+ }
77+
78+ Lazy operator [](std::string_view key) const {
79+ return aref (cpp_to_mrb_value (mrb, key));
80+ }
81+
82+ Lazy operator [](mrb_value key) const {
83+ return aref (key);
84+ }
85+
86+ // Integer index
87+ Lazy operator [](size_t idx) const {
88+ return aref (mrb_convert_number (mrb, idx));
89+ }
90+
91+ // -------------------------------------------------------------------------
92+ // value() — materialize the decoded Ruby value.
93+ // Re-raises any carried error. Raises if called on a nil handle.
94+ // -------------------------------------------------------------------------
95+ mrb_value value () const {
96+ if (has_error_) {
97+ mrb_exc_raise (mrb, exc_);
98+ return mrb_undef_value (); // unreachable
99+ }
100+
101+ mrb_value result = mrb_cbor_lazy_value (mrb, handle_);
102+ if (mrb->exc ) {
103+ mrb_value exc = mrb_obj_value (mrb->exc );
104+ mrb->exc = nullptr ;
105+ mrb_exc_raise (mrb, exc);
106+ }
107+ return result;
108+ }
109+
110+ // -------------------------------------------------------------------------
111+ // operator bool — false if carrying an error or wrapping nil.
112+ // -------------------------------------------------------------------------
113+ explicit operator bool () const {
114+ return !has_error_ && !mrb_undef_p (handle_);
115+ }
116+
117+ bool has_error () const { return has_error_; }
118+
119+ // The underlying CBOR::Lazy mrb_value; mrb_undef_value() on error.
120+ mrb_value raw_handle () const {
121+ return has_error_ ? mrb_undef_value () : handle_;
122+ }
123+
124+ private:
125+ mrb_value handle_;
126+ bool has_error_;
127+ mrb_value exc_;
128+
129+ Lazy aref (mrb_value key) const {
130+ if (has_error_) return Lazy (mrb, exc_, true );
131+
132+ if (mrb_nil_p (handle_)) {
133+ mrb_value exc = mrb_exc_new_str (mrb,
134+ mrb_class_get (mrb, " KeyError" ),
135+ mrb_str_new_lit (mrb, " CBOR::Lazy: index into nil" ));
136+ return Lazy (mrb, exc, true );
137+ }
138+
139+ mrb_value result = mrb_funcall_argv (mrb, handle_, MRB_OPSYM (aref), 1 , &key);
140+
141+ if (mrb->exc ) {
142+ mrb_value exc = mrb_obj_value (mrb->exc );
143+ mrb->exc = nullptr ;
144+ return Lazy (mrb, exc, true );
145+ }
146+
147+ return Lazy (mrb, result);
148+ }
149+ };
150+
151+ // ============================================================================
152+ // decode_lazy
153+ // ============================================================================
154+
155+ inline Lazy decode_lazy (mrb_state *mrb, mrb_value buf) {
156+ mrb_value lazy = mrb_cbor_decode_lazy (mrb, buf);
157+ if (mrb->exc ) {
158+ mrb_value exc = mrb_obj_value (mrb->exc );
159+ mrb->exc = nullptr ;
160+ return Lazy (mrb, exc, true );
161+ }
162+ return Lazy (mrb, lazy);
163+ }
164+
165+ // ============================================================================
166+ // decode
167+ // ============================================================================
168+
169+ inline mrb_value decode (mrb_state *mrb, mrb_value buf) {
170+ return mrb_cbor_decode (mrb, buf);
171+ }
172+
173+ // ============================================================================
174+ // encode
175+ // ============================================================================
176+
177+ inline mrb_value encode (mrb_state *mrb, mrb_value obj, bool sharedrefs = false ) {
178+ return sharedrefs
179+ ? mrb_cbor_encode_sharedrefs (mrb, obj)
180+ : mrb_cbor_encode (mrb, obj);
181+ }
182+
183+ // ============================================================================
184+ // doc_end
185+ // ============================================================================
186+
187+ inline mrb_value doc_end (mrb_state *mrb, mrb_value buf, mrb_int offset = 0 ) {
188+ return mrb_cbor_doc_end (mrb, buf, offset);
189+ }
190+
191+
192+ // ============================================================================
193+ // register_tag
194+ // ============================================================================
195+
196+ inline void register_tag (mrb_state *mrb, mrb_int tag_num, struct RClass *klass) {
197+ mrb_cbor_register_tag (mrb, tag_num, klass);
198+ }
199+
200+ } // namespace CBOR
0 commit comments