Skip to content

Commit c1688cd

Browse files
committed
docs: add listing-005 for complex value handling
1 parent ebb3880 commit c1688cd

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed

listing-005.md

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# Listing 005
2+
3+
This listing continues the Rust-side value conversion, handling collections, ranges, sets, time, regexps, structs, OpenStructs,
4+
and arbitrary objects before introducing `record_variables`, which captures locals from a binding. All snippets originate from
5+
`gems/codetracer-ruby-recorder/ext/native_tracer/src/lib.rs`.
6+
7+
**Recognize a Ruby Array and prepare to iterate over its elements.**
8+
```rust
9+
if RB_TYPE_P(val, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
10+
let len = RARRAY_LEN(val) as usize;
11+
let mut elements = Vec::with_capacity(len);
12+
let ptr = RARRAY_CONST_PTR(val);
13+
```
14+
15+
**Recursively convert each element and accumulate the results.**
16+
```rust
17+
for i in 0..len {
18+
let elem = *ptr.add(i);
19+
elements.push(to_value(recorder, elem, depth - 1));
20+
}
21+
```
22+
23+
**Register the Array type and return a sequence record.**
24+
```rust
25+
let type_id = TraceWriter::ensure_type_id(&mut *recorder.tracer, TypeKind::Seq, "Array");
26+
return ValueRecord::Sequence {
27+
elements,
28+
is_slice: false,
29+
type_id,
30+
};
31+
}
32+
```
33+
34+
**Convert a Ruby Hash by first turning it into an array of pairs.**
35+
```rust
36+
if RB_TYPE_P(val, rb_sys::ruby_value_type::RUBY_T_HASH) {
37+
let pairs = rb_funcall(val, recorder.id.to_a, 0);
38+
let len = RARRAY_LEN(pairs) as usize;
39+
let ptr = RARRAY_CONST_PTR(pairs);
40+
```
41+
42+
**For each pair, build a struct with `k` and `v` fields.**
43+
```rust
44+
let mut elements = Vec::with_capacity(len);
45+
for i in 0..len {
46+
let pair = *ptr.add(i);
47+
if !RB_TYPE_P(pair, rb_sys::ruby_value_type::RUBY_T_ARRAY) || RARRAY_LEN(pair) < 2 {
48+
continue;
49+
}
50+
let pair_ptr = RARRAY_CONST_PTR(pair);
51+
let key = *pair_ptr.add(0);
52+
let val_elem = *pair_ptr.add(1);
53+
elements.push(struct_value(
54+
recorder,
55+
"Pair",
56+
&["k", "v"],
57+
&[key, val_elem],
58+
depth,
59+
));
60+
}
61+
```
62+
63+
**Return the Hash as a sequence of `Pair` structs.**
64+
```rust
65+
let type_id = TraceWriter::ensure_type_id(&mut *recorder.tracer, TypeKind::Seq, "Hash");
66+
return ValueRecord::Sequence {
67+
elements,
68+
is_slice: false,
69+
type_id,
70+
};
71+
}
72+
```
73+
74+
**Ranges serialize their `begin` and `end` values into a struct.**
75+
```rust
76+
if rb_obj_is_kind_of(val, rb_cRange) != 0 {
77+
let begin_val = rb_funcall(val, recorder.id.begin, 0);
78+
let end_val = rb_funcall(val, recorder.id.end, 0);
79+
return struct_value(
80+
recorder,
81+
"Range",
82+
&["begin", "end"],
83+
&[begin_val, end_val],
84+
depth,
85+
);
86+
}
87+
```
88+
89+
**Detect `Set` only once and serialize it as a sequence of members.**
90+
```rust
91+
if NIL_P(recorder.set_class) {
92+
if rb_const_defined(rb_cObject, recorder.id.set_const) != 0 {
93+
recorder.set_class = rb_const_get(rb_cObject, recorder.id.set_const);
94+
}
95+
}
96+
if !NIL_P(recorder.set_class) && rb_obj_is_kind_of(val, recorder.set_class) != 0 {
97+
let arr = rb_funcall(val, recorder.id.to_a, 0);
98+
if RB_TYPE_P(arr, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
99+
let len = RARRAY_LEN(arr) as usize;
100+
let ptr = RARRAY_CONST_PTR(arr);
101+
let mut elements = Vec::with_capacity(len);
102+
for i in 0..len {
103+
let elem = *ptr.add(i);
104+
elements.push(to_value(recorder, elem, depth - 1));
105+
}
106+
let type_id = TraceWriter::ensure_type_id(&mut *recorder.tracer, TypeKind::Seq, "Set");
107+
return ValueRecord::Sequence {
108+
elements,
109+
is_slice: false,
110+
type_id,
111+
};
112+
}
113+
}
114+
```
115+
116+
**Time objects expose seconds and nanoseconds via helper methods.**
117+
```rust
118+
if rb_obj_is_kind_of(val, rb_cTime) != 0 {
119+
let sec = rb_funcall(val, recorder.id.to_i, 0);
120+
let nsec = rb_funcall(val, recorder.id.nsec, 0);
121+
return struct_value(recorder, "Time", &["sec", "nsec"], &[sec, nsec], depth);
122+
}
123+
```
124+
125+
**Regular expressions capture their source pattern and options.**
126+
```rust
127+
if rb_obj_is_kind_of(val, rb_cRegexp) != 0 {
128+
let src = rb_funcall(val, recorder.id.source, 0);
129+
let opts = rb_funcall(val, recorder.id.options, 0);
130+
return struct_value(
131+
recorder,
132+
"Regexp",
133+
&["source", "options"],
134+
&[src, opts],
135+
depth,
136+
);
137+
}
138+
```
139+
140+
**Structs are unpacked by member names and values; unknown layouts fall back to raw.**
141+
```rust
142+
if rb_obj_is_kind_of(val, rb_cStruct) != 0 {
143+
let class_name =
144+
cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Struct".to_string());
145+
let members = rb_funcall(val, recorder.id.members, 0);
146+
let values = rb_funcall(val, recorder.id.values, 0);
147+
if !RB_TYPE_P(members, rb_sys::ruby_value_type::RUBY_T_ARRAY)
148+
|| !RB_TYPE_P(values, rb_sys::ruby_value_type::RUBY_T_ARRAY)
149+
{
150+
let text = value_to_string(recorder, val);
151+
let type_id =
152+
TraceWriter::ensure_type_id(&mut *recorder.tracer, TypeKind::Raw, &class_name);
153+
return ValueRecord::Raw { r: text, type_id };
154+
}
155+
```
156+
157+
**Collect each struct field's name and VALUE pointer before delegation to `struct_value`.**
158+
```rust
159+
let len = RARRAY_LEN(values) as usize;
160+
let mem_ptr = RARRAY_CONST_PTR(members);
161+
let val_ptr = RARRAY_CONST_PTR(values);
162+
let mut names: Vec<&str> = Vec::with_capacity(len);
163+
let mut vals: Vec<VALUE> = Vec::with_capacity(len);
164+
for i in 0..len {
165+
let sym = *mem_ptr.add(i);
166+
let id = rb_sym2id(sym);
167+
let cstr = rb_id2name(id);
168+
let name = CStr::from_ptr(cstr).to_str().unwrap_or("?");
169+
names.push(name);
170+
vals.push(*val_ptr.add(i));
171+
}
172+
return struct_value(recorder, &class_name, &names, &vals, depth);
173+
}
174+
```
175+
176+
**OpenStruct values are converted to hashes and reprocessed.**
177+
```rust
178+
if NIL_P(recorder.open_struct_class) {
179+
if rb_const_defined(rb_cObject, recorder.id.open_struct_const) != 0 {
180+
recorder.open_struct_class = rb_const_get(rb_cObject, recorder.id.open_struct_const);
181+
}
182+
}
183+
if !NIL_P(recorder.open_struct_class) && rb_obj_is_kind_of(val, recorder.open_struct_class) != 0
184+
{
185+
let h = rb_funcall(val, recorder.id.to_h, 0);
186+
return to_value(recorder, h, depth - 1);
187+
}
188+
```
189+
190+
**For generic objects, collect instance variables or fall back to a raw string.**
191+
```rust
192+
let class_name = cstr_to_string(rb_obj_classname(val)).unwrap_or_else(|| "Object".to_string());
193+
// generic object
194+
let ivars = rb_funcall(val, recorder.id.instance_variables, 0);
195+
if !RB_TYPE_P(ivars, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
196+
let text = value_to_string(recorder, val);
197+
let type_id =
198+
TraceWriter::ensure_type_id(&mut *recorder.tracer, TypeKind::Raw, &class_name);
199+
return ValueRecord::Raw { r: text, type_id };
200+
}
201+
```
202+
203+
**Map each instance variable name to its value and emit a struct if any exist.**
204+
```rust
205+
let len = RARRAY_LEN(ivars) as usize;
206+
let ptr = RARRAY_CONST_PTR(ivars);
207+
let mut names: Vec<&str> = Vec::with_capacity(len);
208+
let mut vals: Vec<VALUE> = Vec::with_capacity(len);
209+
for i in 0..len {
210+
let sym = *ptr.add(i);
211+
let id = rb_sym2id(sym);
212+
let cstr = rb_id2name(id);
213+
let name = CStr::from_ptr(cstr).to_str().unwrap_or("?");
214+
names.push(name);
215+
let value = rb_funcall(val, recorder.id.instance_variable_get, 1, sym);
216+
vals.push(value);
217+
}
218+
if !names.is_empty() {
219+
return struct_value(recorder, &class_name, &names, &vals, depth);
220+
}
221+
let text = value_to_string(recorder, val);
222+
let type_id = TraceWriter::ensure_type_id(&mut *recorder.tracer, TypeKind::Raw, &class_name);
223+
ValueRecord::Raw { r: text, type_id }
224+
}
225+
```
226+
227+
**`record_variables` pulls local variable names from a binding.**
228+
```rust
229+
unsafe fn record_variables(recorder: &mut Recorder, binding: VALUE) -> Vec<FullValueRecord> {
230+
let vars = rb_funcall(binding, recorder.id.local_variables, 0);
231+
if !RB_TYPE_P(vars, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
232+
return Vec::new();
233+
}
234+
```
235+
236+
**Iterate over each variable, converting and registering its value.**
237+
```rust
238+
let len = RARRAY_LEN(vars) as usize;
239+
let mut result = Vec::with_capacity(len);
240+
let ptr = RARRAY_CONST_PTR(vars);
241+
for i in 0..len {
242+
let sym = *ptr.add(i);
243+
let id = rb_sym2id(sym);
244+
let name = CStr::from_ptr(rb_id2name(id)).to_str().unwrap_or("");
245+
let value = rb_funcall(binding, recorder.id.local_variable_get, 1, sym);
246+
let val_rec = to_value(recorder, value, 10);
247+
TraceWriter::register_variable_with_full_value(
248+
&mut *recorder.tracer,
249+
name,
250+
val_rec.clone(),
251+
);
252+
let var_id = TraceWriter::ensure_variable_id(&mut *recorder.tracer, name);
253+
result.push(FullValueRecord {
254+
variable_id: var_id,
255+
value: val_rec,
256+
});
257+
}
258+
result
259+
}
260+
```

0 commit comments

Comments
 (0)