Skip to content

Commit 24ba04c

Browse files
committed
add c and c++ api
1 parent 9830098 commit 24ba04c

4 files changed

Lines changed: 310 additions & 46 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ build-iPhoneSimulator/
5454

5555
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
5656
# .rubocop-https?--*
57-
mruby
57+
/mruby/
58+
/fuzz/mruby/
5859
.cache
5960
build_config.rb.lock
6061
compile_commands.json

include/mruby/cbor.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#pragma once
2+
3+
/*
4+
* mruby-cbor C API
5+
*
6+
* Include this header from C or C++ code that links against mruby-cbor.
7+
* For the higher-level C++ proxy API, include <mruby/cbor.hpp> instead.
8+
*/
9+
10+
#include <mruby.h>
11+
12+
MRB_BEGIN_DECL
13+
14+
/*
15+
* Encoding
16+
*/
17+
18+
/* Encode obj to a CBOR byte string (mrb_value of type String). */
19+
MRB_API mrb_value mrb_cbor_encode(mrb_state *mrb, mrb_value obj);
20+
21+
/* Encode obj with shared-reference tags (Tag 28/29) for deduplication
22+
* and cyclic-structure support. */
23+
MRB_API mrb_value mrb_cbor_encode_sharedrefs(mrb_state *mrb, mrb_value obj);
24+
25+
/*
26+
* Decoding
27+
*/
28+
29+
/* Eagerly decode a CBOR byte string into a Ruby value.
30+
* buf must be a String mrb_value; */
31+
MRB_API mrb_value mrb_cbor_decode(mrb_state *mrb, mrb_value buf);
32+
33+
/* Return a CBOR::Lazy object that wraps buf without decoding it. */
34+
MRB_API mrb_value mrb_cbor_decode_lazy(mrb_state *mrb, mrb_value buf);
35+
36+
/* Materialize a CBOR::Lazy object into a fully decoded Ruby value.
37+
* lazy must be a CBOR::Lazy instance returned from mrb_cbor_decode_lazy. */
38+
MRB_API mrb_value mrb_cbor_lazy_value(mrb_state *mrb, mrb_value lazy);
39+
40+
/*
41+
* Streaming / framing
42+
*/
43+
44+
/* Find the byte offset of the end of the first complete CBOR document in buf
45+
* starting at offset. Returns an Integer offset on success or nil if the
46+
* buffer does not yet contain a complete document. */
47+
MRB_API mrb_value mrb_cbor_doc_end(mrb_state *mrb, mrb_value buf, mrb_int offset);
48+
49+
/*
50+
* Tag registration
51+
*/
52+
53+
/* Register klass as the Ruby class for CBOR tag tag_num.
54+
* Equivalent to CBOR.register_tag(tag_num, klass) from Ruby. */
55+
MRB_API void mrb_cbor_register_tag(mrb_state *mrb, uint64_t tag_num, struct RClass *klass);
56+
57+
MRB_END_DECL

include/mruby/cbor.hpp

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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

Comments
 (0)