c0data 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +143 -0
- data/c0-c/c0.h +3926 -0
- data/ext/c0/c0_ext.c +1061 -0
- data/ext/c0/extconf.rb +7 -0
- data/lib/c0/body.rb +314 -0
- data/lib/c0/builder.rb +64 -0
- data/lib/c0/document.rb +243 -0
- data/lib/c0/pretty.rb +28 -0
- data/lib/c0/stream.rb +76 -0
- data/lib/c0.rb +123 -0
- metadata +53 -0
data/ext/c0/c0_ext.c
ADDED
|
@@ -0,0 +1,1061 @@
|
|
|
1
|
+
/* Ruby C extension: a thin binding over the c0 single-header C core (1.0).
|
|
2
|
+
*
|
|
3
|
+
* Two layers live here:
|
|
4
|
+
*
|
|
5
|
+
* C0::Ext module functions that walk a Ruby String with the c0.h
|
|
6
|
+
* navigators and hand back byte offsets, so the Ruby layer can
|
|
7
|
+
* return shared (zero-copy) byteslices into the caller's buffer.
|
|
8
|
+
* Every range is bounds-checked against the string as it is
|
|
9
|
+
* now, so a mutated buffer can never make us read out of range.
|
|
10
|
+
*
|
|
11
|
+
* C0::Builder a TypedData object owning a c0_builder (init/free pair), with
|
|
12
|
+
* the whole writer API: the primitives (one method per code,
|
|
13
|
+
* fs gs rs us sub soh dle can bel enq syn eot etb em) and the
|
|
14
|
+
* composed methods built on them, block forms included.
|
|
15
|
+
*
|
|
16
|
+
* Errors become exceptions deriving from C0::Error, with the byte position on
|
|
17
|
+
* the syntax errors. Malloc'd results from c0.h (pretty form) are copied into
|
|
18
|
+
* Ruby strings and freed before returning.
|
|
19
|
+
*/
|
|
20
|
+
#include "ruby.h"
|
|
21
|
+
#include "ruby/encoding.h"
|
|
22
|
+
|
|
23
|
+
#define C0_IMPLEMENTATION
|
|
24
|
+
#include "c0.h"
|
|
25
|
+
|
|
26
|
+
static VALUE mC0, mExt, mPretty, cBuilder;
|
|
27
|
+
static VALUE eError, eSyntaxError, eUnassignedCodeError, eUnexpectedEndError, eKeyError, eBuildError,
|
|
28
|
+
eGlyphInDataError;
|
|
29
|
+
|
|
30
|
+
/* ---- buffers and ranges -------------------------------------------------- */
|
|
31
|
+
|
|
32
|
+
typedef struct {
|
|
33
|
+
VALUE str;
|
|
34
|
+
const uint8_t *p;
|
|
35
|
+
size_t len;
|
|
36
|
+
} buf_t;
|
|
37
|
+
|
|
38
|
+
static buf_t get_buf(VALUE str) {
|
|
39
|
+
buf_t b;
|
|
40
|
+
StringValue(str);
|
|
41
|
+
b.str = str;
|
|
42
|
+
b.p = (const uint8_t *)RSTRING_PTR(str);
|
|
43
|
+
b.len = (size_t)RSTRING_LEN(str);
|
|
44
|
+
return b;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* [s, e) must lie inside the buffer. */
|
|
48
|
+
static void check_range(buf_t b, long s, long e) {
|
|
49
|
+
if (s < 0 || e < s || (size_t)e > b.len) {
|
|
50
|
+
rb_raise(rb_eRangeError, "byte range %ld...%ld is outside the buffer (%ld bytes)", s, e, (long)b.len);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static c0_bytes slice_of(buf_t b, VALUE vs, VALUE ve) {
|
|
55
|
+
long s = NUM2LONG(vs), e = NUM2LONG(ve);
|
|
56
|
+
check_range(b, s, e);
|
|
57
|
+
return c0_bytes_of(b.p + s, (size_t)(e - s));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* A [start, end] pair for a c0_bytes slice of the buffer. */
|
|
61
|
+
static VALUE span(buf_t b, c0_bytes x) {
|
|
62
|
+
long s = (long)(x.ptr - b.p);
|
|
63
|
+
return rb_ary_new3(2, LONG2NUM(s), LONG2NUM(s + (long)x.len));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* A shared byteslice of the buffer (zero-copy for long strings). */
|
|
67
|
+
static VALUE substr(buf_t b, const uint8_t *ptr, size_t len) {
|
|
68
|
+
VALUE s = rb_str_substr(b.str, (long)(ptr - b.p), (long)len);
|
|
69
|
+
return rb_enc_associate(s, rb_ascii8bit_encoding());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static VALUE bin_str(const void *p, size_t n) {
|
|
73
|
+
return rb_enc_associate(rb_str_new((const char *)p, (long)n), rb_ascii8bit_encoding());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ---- errors ----------------------------------------------------------- */
|
|
77
|
+
|
|
78
|
+
static VALUE error_class(c0_status code) {
|
|
79
|
+
switch (code) {
|
|
80
|
+
case C0_ERR_UNASSIGNED: return eUnassignedCodeError;
|
|
81
|
+
case C0_ERR_UNEXPECTED_END: return eUnexpectedEndError;
|
|
82
|
+
case C0_ERR_SYNTAX:
|
|
83
|
+
case C0_ERR_TOO_DEEP: return eSyntaxError;
|
|
84
|
+
case C0_ERR_NOT_FOUND: return eKeyError;
|
|
85
|
+
case C0_ERR_BAD_NAME:
|
|
86
|
+
case C0_ERR_BAD_STATE: return eBuildError;
|
|
87
|
+
default: return eError;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static const char *status_message(c0_status code) {
|
|
92
|
+
switch (code) {
|
|
93
|
+
case C0_ERR_SYNTAX: return "malformed input";
|
|
94
|
+
case C0_ERR_UNASSIGNED: return "unassigned control code";
|
|
95
|
+
case C0_ERR_UNEXPECTED_END: return "unexpected end of input";
|
|
96
|
+
case C0_ERR_TOO_DEEP: return "levels nested too deep";
|
|
97
|
+
case C0_ERR_NOT_SCALAR: return "unit is not a scalar (a nested level or a reference)";
|
|
98
|
+
case C0_ERR_NOT_LEVEL: return "unit is not a nested level";
|
|
99
|
+
case C0_ERR_NOT_LIST: return "level is not a list";
|
|
100
|
+
case C0_ERR_NOT_REFERENCE: return "unit is not a reference";
|
|
101
|
+
case C0_ERR_NEEDS_DECODE: return "text needs decoding";
|
|
102
|
+
case C0_ERR_NOT_FOUND: return "not found";
|
|
103
|
+
case C0_ERR_BAD_NAME: return "not a legal name (a control byte or edge whitespace)";
|
|
104
|
+
case C0_ERR_BAD_STATE: return "builder misuse";
|
|
105
|
+
case C0_ERR_OOM: return "out of memory";
|
|
106
|
+
default: return "error";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Raise for a c0_error with position detail. */
|
|
111
|
+
static void raise_error(const c0_error *err) {
|
|
112
|
+
VALUE exc;
|
|
113
|
+
if (err->code == C0_ERR_OOM) rb_memerror();
|
|
114
|
+
exc = rb_exc_new_str(error_class(err->code),
|
|
115
|
+
rb_sprintf("%s at byte %ld", err->msg && *err->msg ? err->msg : status_message(err->code),
|
|
116
|
+
(long)err->pos));
|
|
117
|
+
rb_ivar_set(exc, rb_intern("@position"), LONG2NUM((long)err->pos));
|
|
118
|
+
rb_exc_raise(exc);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Raise for a bare status (no position). */
|
|
122
|
+
static void raise_status(c0_status code) {
|
|
123
|
+
if (code == C0_ERR_OOM) rb_memerror();
|
|
124
|
+
rb_raise(error_class(code), "%s", status_message(code));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ---- bytewise helpers --------------------------------------------------- */
|
|
128
|
+
|
|
129
|
+
static VALUE ext_is_assigned(VALUE self, VALUE byte) {
|
|
130
|
+
(void)self;
|
|
131
|
+
return c0_is_assigned((uint8_t)NUM2INT(byte)) ? Qtrue : Qfalse;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static VALUE ext_is_layout(VALUE self, VALUE byte) {
|
|
135
|
+
(void)self;
|
|
136
|
+
return c0_is_layout((uint8_t)NUM2INT(byte)) ? Qtrue : Qfalse;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static VALUE ext_is_plain(VALUE self, VALUE str) {
|
|
140
|
+
buf_t b = get_buf(str);
|
|
141
|
+
(void)self;
|
|
142
|
+
return c0_is_plain(b.p, b.len) ? Qtrue : Qfalse;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static VALUE ext_trim(VALUE self, VALUE str) {
|
|
146
|
+
buf_t b = get_buf(str);
|
|
147
|
+
c0_bytes t = c0_trim(c0_bytes_of(b.p, b.len));
|
|
148
|
+
(void)self;
|
|
149
|
+
return substr(b, t.ptr, t.len);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static VALUE ext_decode_text(VALUE self, VALUE str) {
|
|
153
|
+
buf_t b = get_buf(str);
|
|
154
|
+
VALUE out = rb_str_buf_new((long)b.len);
|
|
155
|
+
size_t n = c0_decode_text(b.p, b.len, (uint8_t *)RSTRING_PTR(out));
|
|
156
|
+
(void)self;
|
|
157
|
+
rb_str_set_len(out, (long)n);
|
|
158
|
+
return rb_enc_associate(out, rb_ascii8bit_encoding());
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* ---- validation --------------------------------------------------------- */
|
|
162
|
+
|
|
163
|
+
static VALUE ext_validate(VALUE self, VALUE str) {
|
|
164
|
+
buf_t b = get_buf(str);
|
|
165
|
+
c0_error err;
|
|
166
|
+
(void)self;
|
|
167
|
+
if (c0_validate(b.p, b.len, &err) != C0_OK) raise_error(&err);
|
|
168
|
+
return Qnil;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static VALUE ext_canonical(VALUE self, VALUE str) {
|
|
172
|
+
buf_t b = get_buf(str);
|
|
173
|
+
(void)self;
|
|
174
|
+
return c0_canonical(b.p, b.len) ? Qtrue : Qfalse;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ---- units and values --------------------------------------------------- */
|
|
178
|
+
|
|
179
|
+
static VALUE ext_unit_type(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
180
|
+
buf_t b = get_buf(str);
|
|
181
|
+
(void)self;
|
|
182
|
+
return INT2NUM((int)c0_unit_type(slice_of(b, s, e)));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* The logical value of the raw unit at [s, e): a shared byteslice when no
|
|
186
|
+
* decoding is needed, a fresh string otherwise. */
|
|
187
|
+
static VALUE ext_value(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
188
|
+
buf_t b = get_buf(str);
|
|
189
|
+
c0_bytes raw = slice_of(b, s, e), v;
|
|
190
|
+
c0_status st = c0_value(raw, NULL, &v);
|
|
191
|
+
(void)self;
|
|
192
|
+
if (st == C0_ERR_NEEDS_DECODE) {
|
|
193
|
+
VALUE out = rb_str_buf_new((long)raw.len);
|
|
194
|
+
st = c0_value(raw, (uint8_t *)RSTRING_PTR(out), &v);
|
|
195
|
+
if (st != C0_OK) raise_status(st);
|
|
196
|
+
rb_str_set_len(out, (long)v.len);
|
|
197
|
+
return rb_enc_associate(out, rb_ascii8bit_encoding());
|
|
198
|
+
}
|
|
199
|
+
if (st != C0_OK) raise_status(st);
|
|
200
|
+
return substr(b, v.ptr, v.len);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* The raw unit at [s, e) as a nested level: the body's [start, stop) in the buffer. */
|
|
204
|
+
static VALUE ext_unit_level(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
205
|
+
buf_t b = get_buf(str);
|
|
206
|
+
c0_body body;
|
|
207
|
+
c0_status st = c0_unit_level(slice_of(b, s, e), &body);
|
|
208
|
+
long base;
|
|
209
|
+
(void)self;
|
|
210
|
+
if (st != C0_OK) raise_status(st);
|
|
211
|
+
base = (long)(body.buf - b.p);
|
|
212
|
+
return rb_ary_new3(2, LONG2NUM(base + (long)body.start), LONG2NUM(base + (long)body.stop));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* The raw unit at [s, e) as a reference: its segments as [start, end] pairs. */
|
|
216
|
+
static VALUE ext_unit_ref(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
217
|
+
buf_t b = get_buf(str);
|
|
218
|
+
c0_ref ref;
|
|
219
|
+
c0_status st = c0_unit_ref(slice_of(b, s, e), &ref);
|
|
220
|
+
VALUE out;
|
|
221
|
+
size_t i;
|
|
222
|
+
(void)self;
|
|
223
|
+
if (st != C0_OK) raise_status(st);
|
|
224
|
+
out = rb_ary_new_capa((long)ref.count);
|
|
225
|
+
for (i = 0; i < ref.count; i++) rb_ary_push(out, span(b, ref.seg[i]));
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static VALUE collect_units(buf_t b, c0_unit_iter *it) {
|
|
230
|
+
VALUE out = rb_ary_new();
|
|
231
|
+
c0_bytes raw;
|
|
232
|
+
while (c0_next_unit(it, &raw)) rb_ary_push(out, span(b, raw));
|
|
233
|
+
if (it->err.code) raise_error(&it->err);
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* The raw unit at [s, e) as a flat list: its items' raw ranges. */
|
|
238
|
+
static VALUE ext_unit_list(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
239
|
+
buf_t b = get_buf(str);
|
|
240
|
+
c0_unit_iter it;
|
|
241
|
+
c0_status st = c0_unit_list(slice_of(b, s, e), &it);
|
|
242
|
+
(void)self;
|
|
243
|
+
if (st != C0_OK) raise_status(st);
|
|
244
|
+
return collect_units(b, &it);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* ---- records ------------------------------------------------------------ */
|
|
248
|
+
|
|
249
|
+
static c0_record record_of(buf_t b, VALUE s, VALUE e) {
|
|
250
|
+
c0_record r;
|
|
251
|
+
long start = NUM2LONG(s), stop = NUM2LONG(e);
|
|
252
|
+
check_range(b, start, stop);
|
|
253
|
+
r.buf = b.p;
|
|
254
|
+
r.start = (size_t)start;
|
|
255
|
+
r.stop = (size_t)stop;
|
|
256
|
+
return r;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static VALUE ext_record_keyed(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
260
|
+
buf_t b = get_buf(str);
|
|
261
|
+
(void)self;
|
|
262
|
+
return c0_record_keyed(record_of(b, s, e)) ? Qtrue : Qfalse;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Positional units of the record at [s, e): raw ranges. */
|
|
266
|
+
static VALUE ext_record_units(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
267
|
+
buf_t b = get_buf(str);
|
|
268
|
+
c0_unit_iter it = c0_record_units(record_of(b, s, e));
|
|
269
|
+
(void)self;
|
|
270
|
+
return collect_units(b, &it);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Keyed units of the record (or heading) at [s, e): [ks, ke, vs, ve] each. */
|
|
274
|
+
static VALUE ext_record_pairs(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
275
|
+
buf_t b = get_buf(str);
|
|
276
|
+
c0_pair_iter it = c0_record_pairs(record_of(b, s, e));
|
|
277
|
+
VALUE out = rb_ary_new();
|
|
278
|
+
c0_bytes k, v;
|
|
279
|
+
(void)self;
|
|
280
|
+
while (c0_next_pair(&it, &k, &v)) {
|
|
281
|
+
long ks = (long)(k.ptr - b.p), vs = (long)(v.ptr - b.p);
|
|
282
|
+
rb_ary_push(out, rb_ary_new3(4, LONG2NUM(ks), LONG2NUM(ks + (long)k.len), LONG2NUM(vs),
|
|
283
|
+
LONG2NUM(vs + (long)v.len)));
|
|
284
|
+
}
|
|
285
|
+
if (it.err.code) raise_error(&it.err);
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* ---- bodies ------------------------------------------------------------- */
|
|
290
|
+
|
|
291
|
+
static c0_body body_of(buf_t b, VALUE s, VALUE e) {
|
|
292
|
+
c0_body body;
|
|
293
|
+
long start = NUM2LONG(s), stop = NUM2LONG(e);
|
|
294
|
+
check_range(b, start, stop);
|
|
295
|
+
body.buf = b.p;
|
|
296
|
+
body.start = (size_t)start;
|
|
297
|
+
body.stop = (size_t)stop;
|
|
298
|
+
return body;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* Files of the body at [s, e): [name_start, name_end, start, stop] each (name trimmed). */
|
|
302
|
+
static VALUE ext_files(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
303
|
+
buf_t b = get_buf(str);
|
|
304
|
+
c0_file_iter it = c0_body_files(body_of(b, s, e));
|
|
305
|
+
c0_file f;
|
|
306
|
+
VALUE out = rb_ary_new();
|
|
307
|
+
(void)self;
|
|
308
|
+
while (c0_next_file(&it, &f)) {
|
|
309
|
+
c0_bytes n = c0_file_name(f);
|
|
310
|
+
long ns = (long)(n.ptr - b.p);
|
|
311
|
+
rb_ary_push(out, rb_ary_new3(4, LONG2NUM(ns), LONG2NUM(ns + (long)n.len), LONG2NUM((long)f.start),
|
|
312
|
+
LONG2NUM((long)f.stop)));
|
|
313
|
+
}
|
|
314
|
+
if (it.err.code) raise_error(&it.err);
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Groups of the range [s, e) (a body or one file): [depth, name_start, name_end, start, stop] each. */
|
|
319
|
+
static VALUE ext_groups(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
320
|
+
buf_t b = get_buf(str);
|
|
321
|
+
c0_group_iter it = c0_body_groups(body_of(b, s, e));
|
|
322
|
+
c0_group g;
|
|
323
|
+
VALUE out = rb_ary_new();
|
|
324
|
+
(void)self;
|
|
325
|
+
while (c0_next_group(&it, &g)) {
|
|
326
|
+
c0_bytes n = c0_group_name(g);
|
|
327
|
+
long ns = (long)(n.ptr - b.p);
|
|
328
|
+
rb_ary_push(out, rb_ary_new3(5, LONG2NUM((long)g.depth), LONG2NUM(ns), LONG2NUM(ns + (long)n.len),
|
|
329
|
+
LONG2NUM((long)g.start), LONG2NUM((long)g.stop)));
|
|
330
|
+
}
|
|
331
|
+
if (it.err.code) raise_error(&it.err);
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
static c0_group group_of(buf_t b, VALUE s, VALUE e) {
|
|
336
|
+
c0_group g;
|
|
337
|
+
long start = NUM2LONG(s), stop = NUM2LONG(e);
|
|
338
|
+
check_range(b, start, stop);
|
|
339
|
+
g.buf = b.p;
|
|
340
|
+
g.depth = 0;
|
|
341
|
+
g.name_start = g.name_end = (size_t)start;
|
|
342
|
+
g.start = (size_t)start;
|
|
343
|
+
g.stop = (size_t)stop;
|
|
344
|
+
return g;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* The header row of the group whose contents are [s, e): a record range, or nil. */
|
|
348
|
+
static VALUE ext_group_header(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
349
|
+
buf_t b = get_buf(str);
|
|
350
|
+
c0_record h;
|
|
351
|
+
(void)self;
|
|
352
|
+
if (!c0_group_header(group_of(b, s, e), &h)) return Qnil;
|
|
353
|
+
return rb_ary_new3(2, LONG2NUM((long)h.start), LONG2NUM((long)h.stop));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Live records of the group whose contents are [s, e): record ranges. */
|
|
357
|
+
static VALUE ext_group_records(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
358
|
+
buf_t b = get_buf(str);
|
|
359
|
+
c0_record_iter it = c0_group_records(group_of(b, s, e));
|
|
360
|
+
c0_record r;
|
|
361
|
+
VALUE out = rb_ary_new();
|
|
362
|
+
(void)self;
|
|
363
|
+
while (c0_next_record(&it, &r)) {
|
|
364
|
+
rb_ary_push(out, rb_ary_new3(2, LONG2NUM((long)r.start), LONG2NUM((long)r.stop)));
|
|
365
|
+
}
|
|
366
|
+
if (it.err.code) raise_error(&it.err);
|
|
367
|
+
return out;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
static VALUE ext_body_bare(VALUE self, VALUE str, VALUE s, VALUE e) {
|
|
371
|
+
buf_t b = get_buf(str);
|
|
372
|
+
(void)self;
|
|
373
|
+
return c0_body_bare(body_of(b, s, e)) ? Qtrue : Qfalse;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* ---- documents ---------------------------------------------------------- */
|
|
377
|
+
|
|
378
|
+
static VALUE document_array(const c0_document *d) {
|
|
379
|
+
return rb_ary_new3(9, LONG2NUM((long)d->start), LONG2NUM((long)d->stop), d->has_syn ? Qtrue : Qfalse,
|
|
380
|
+
d->has_eot ? Qtrue : Qfalse, LONG2NUM((long)d->heading_start),
|
|
381
|
+
LONG2NUM((long)d->heading_end), LONG2NUM((long)d->body.start),
|
|
382
|
+
LONG2NUM((long)d->body.stop), UINT2NUM(d->warnings));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* The document at `start`: [start, stop, syn?, eot?, heading_start,
|
|
386
|
+
* heading_end, body_start, body_stop, warning_bits]. */
|
|
387
|
+
static VALUE ext_document(VALUE self, VALUE str, VALUE start) {
|
|
388
|
+
buf_t b = get_buf(str);
|
|
389
|
+
c0_document d;
|
|
390
|
+
c0_error err;
|
|
391
|
+
long s = NUM2LONG(start);
|
|
392
|
+
(void)self;
|
|
393
|
+
check_range(b, s, s);
|
|
394
|
+
if (c0_document_read(b.p, b.len, (size_t)s, &d, &err) != C0_OK) raise_error(&err);
|
|
395
|
+
return document_array(&d);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* Every document of a container: [[doc...], ..., em?, em_pos]. */
|
|
399
|
+
static VALUE ext_documents(VALUE self, VALUE str) {
|
|
400
|
+
buf_t b = get_buf(str);
|
|
401
|
+
c0_doc_iter it = c0_documents(b.p, b.len);
|
|
402
|
+
c0_document d;
|
|
403
|
+
VALUE docs = rb_ary_new();
|
|
404
|
+
(void)self;
|
|
405
|
+
while (c0_next_document(&it, &d)) rb_ary_push(docs, document_array(&d));
|
|
406
|
+
if (it.err.code) raise_error(&it.err);
|
|
407
|
+
return rb_ary_new3(3, docs, it.has_em ? Qtrue : Qfalse, LONG2NUM((long)it.em_pos));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* ---- streams ------------------------------------------------------------ */
|
|
411
|
+
|
|
412
|
+
/* [committed_end, torn?, [[start, end, damaged?], ...]] */
|
|
413
|
+
static VALUE ext_stream(VALUE self, VALUE str) {
|
|
414
|
+
buf_t b = get_buf(str);
|
|
415
|
+
c0_stream s = c0_stream_read(b.p, b.len);
|
|
416
|
+
c0_block_iter it = c0_stream_blocks(&s);
|
|
417
|
+
c0_bytes blk;
|
|
418
|
+
int damaged;
|
|
419
|
+
VALUE blocks = rb_ary_new_capa((long)s.block_count);
|
|
420
|
+
(void)self;
|
|
421
|
+
while (c0_next_block(&it, &blk, &damaged)) {
|
|
422
|
+
long bs = (long)(blk.ptr - b.p);
|
|
423
|
+
rb_ary_push(blocks, rb_ary_new3(3, LONG2NUM(bs), LONG2NUM(bs + (long)blk.len), damaged ? Qtrue : Qfalse));
|
|
424
|
+
}
|
|
425
|
+
return rb_ary_new3(3, LONG2NUM((long)s.committed_end), s.torn ? Qtrue : Qfalse, blocks);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* The undamaged committed blocks, ETB markers and payloads removed. */
|
|
429
|
+
static VALUE ext_stream_undamaged(VALUE self, VALUE str) {
|
|
430
|
+
buf_t b = get_buf(str);
|
|
431
|
+
c0_stream s = c0_stream_read(b.p, b.len);
|
|
432
|
+
VALUE out = rb_str_buf_new((long)s.committed_end + 1);
|
|
433
|
+
size_t n = c0_stream_undamaged(&s, (uint8_t *)RSTRING_PTR(out));
|
|
434
|
+
(void)self;
|
|
435
|
+
rb_str_set_len(out, (long)n);
|
|
436
|
+
return rb_enc_associate(out, rb_ascii8bit_encoding());
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
static VALUE ext_last_commit_end(VALUE self, VALUE str) {
|
|
440
|
+
buf_t b = get_buf(str);
|
|
441
|
+
(void)self;
|
|
442
|
+
return LONG2NUM((long)c0_stream_last_commit_end(b.p, b.len));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* ---- pretty form -------------------------------------------------------- */
|
|
446
|
+
|
|
447
|
+
static VALUE ext_pretty_format(VALUE self, VALUE str, VALUE indent, VALUE mode) {
|
|
448
|
+
buf_t b = get_buf(str);
|
|
449
|
+
const char *ind = NIL_P(indent) ? NULL : StringValueCStr(indent);
|
|
450
|
+
int m = NUM2INT(mode);
|
|
451
|
+
size_t outlen;
|
|
452
|
+
c0_error err;
|
|
453
|
+
char *p;
|
|
454
|
+
VALUE out;
|
|
455
|
+
(void)self;
|
|
456
|
+
if (m < C0_PRETTY_COMPACT || m > C0_PRETTY_SPACED) rb_raise(rb_eArgError, "unknown pretty mode %d", m);
|
|
457
|
+
p = c0_pretty_format(b.p, b.len, ind, (c0_pretty_mode)m, &outlen, &err);
|
|
458
|
+
if (!p) {
|
|
459
|
+
if (err.code == C0_ERR_SYNTAX && err.msg && strcmp(err.msg, "glyph in data") == 0) {
|
|
460
|
+
rb_raise(eGlyphInDataError,
|
|
461
|
+
"value contains a Control Picture character and cannot be shown in pretty form (byte %ld)",
|
|
462
|
+
(long)err.pos);
|
|
463
|
+
}
|
|
464
|
+
raise_error(&err);
|
|
465
|
+
}
|
|
466
|
+
out = rb_utf8_str_new(p, (long)outlen);
|
|
467
|
+
free(p);
|
|
468
|
+
return out;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static VALUE ext_pretty_parse(VALUE self, VALUE str) {
|
|
472
|
+
buf_t b = get_buf(str);
|
|
473
|
+
size_t outlen;
|
|
474
|
+
uint8_t *p = c0_pretty_parse((const char *)b.p, b.len, &outlen);
|
|
475
|
+
VALUE out;
|
|
476
|
+
(void)self;
|
|
477
|
+
if (!p) rb_memerror();
|
|
478
|
+
out = bin_str(p, outlen);
|
|
479
|
+
free(p);
|
|
480
|
+
return out;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* ---- Builder (TypedData over c0_builder) -------------------------------- */
|
|
484
|
+
|
|
485
|
+
static void builder_free(void *p) {
|
|
486
|
+
c0_builder_free((c0_builder *)p);
|
|
487
|
+
xfree(p);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
static size_t builder_memsize(const void *p) {
|
|
491
|
+
const c0_builder *b = (const c0_builder *)p;
|
|
492
|
+
return sizeof(*b) + b->cap + b->stack_cap * 2 * sizeof(int);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
static const rb_data_type_t builder_type = {
|
|
496
|
+
"C0::Builder",
|
|
497
|
+
{NULL, builder_free, builder_memsize, NULL, {NULL}},
|
|
498
|
+
NULL,
|
|
499
|
+
NULL,
|
|
500
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
static VALUE builder_alloc(VALUE klass) {
|
|
504
|
+
c0_builder *b;
|
|
505
|
+
VALUE obj = TypedData_Make_Struct(klass, c0_builder, &builder_type, b);
|
|
506
|
+
c0_builder_init(b);
|
|
507
|
+
return obj;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
static c0_builder *get_builder(VALUE self) {
|
|
511
|
+
c0_builder *b;
|
|
512
|
+
TypedData_Get_Struct(self, c0_builder, &builder_type, b);
|
|
513
|
+
return b;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* After a write: a sticky error status becomes an exception. */
|
|
517
|
+
static void builder_check(c0_builder *b) {
|
|
518
|
+
if (b->status != C0_OK) {
|
|
519
|
+
c0_status st = b->status;
|
|
520
|
+
b->status = C0_OK; /* the builder stays usable; the bad call did not land */
|
|
521
|
+
raise_status(st);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* A Ruby value as bytes (Symbols become their names). */
|
|
526
|
+
static c0_bytes bytes_arg(VALUE v) {
|
|
527
|
+
if (SYMBOL_P(v)) v = rb_sym2str(v);
|
|
528
|
+
StringValue(v);
|
|
529
|
+
return c0_bytes_of((const uint8_t *)RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* A name: checked before anything is written, so a bad one leaves no bytes. */
|
|
533
|
+
static c0_bytes name_arg(VALUE v, const char *what) {
|
|
534
|
+
c0_bytes n = bytes_arg(v);
|
|
535
|
+
if (!c0_is_name(n.ptr, n.len)) {
|
|
536
|
+
rb_raise(eBuildError, "%s is not a legal name (no control bytes, no edge whitespace): %+" PRIsVALUE, what,
|
|
537
|
+
SYMBOL_P(v) ? rb_sym2str(v) : v);
|
|
538
|
+
}
|
|
539
|
+
return n;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* Splat or one Array: the elements as an Array. */
|
|
543
|
+
static VALUE splat_or_array(int argc, VALUE *argv) {
|
|
544
|
+
if (argc == 1 && RB_TYPE_P(argv[0], T_ARRAY)) return argv[0];
|
|
545
|
+
return rb_ary_new_from_values(argc, argv);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/* c0_bytes for each element of an Array (names when `names`). The array is
|
|
549
|
+
* backed by a GC-managed temporary String stored in *tmp; the caller keeps
|
|
550
|
+
* `tmp` alive (RB_GC_GUARD) until it is done with the pointer. */
|
|
551
|
+
static c0_bytes *bytes_array(VALUE ary, int names, const char *what, VALUE *tmp, long *count) {
|
|
552
|
+
long i, n = RARRAY_LEN(ary);
|
|
553
|
+
c0_bytes *out;
|
|
554
|
+
*tmp = rb_str_tmp_new((long)((n > 0 ? n : 1) * sizeof(c0_bytes)));
|
|
555
|
+
out = (c0_bytes *)RSTRING_PTR(*tmp);
|
|
556
|
+
for (i = 0; i < n; i++) {
|
|
557
|
+
VALUE v = RARRAY_AREF(ary, i);
|
|
558
|
+
out[i] = names ? name_arg(v, what) : bytes_arg(v);
|
|
559
|
+
}
|
|
560
|
+
*count = n;
|
|
561
|
+
return out;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
static VALUE builder_initialize(VALUE self) {
|
|
565
|
+
(void)self;
|
|
566
|
+
return self;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* bytes → a fresh binary String of what has been written so far. */
|
|
570
|
+
static VALUE builder_bytes(VALUE self) {
|
|
571
|
+
c0_builder *b = get_builder(self);
|
|
572
|
+
c0_bytes out;
|
|
573
|
+
builder_check(b);
|
|
574
|
+
out = c0_builder_bytes(b);
|
|
575
|
+
return bin_str(out.ptr, out.len);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
static VALUE builder_size(VALUE self) { return LONG2NUM((long)get_builder(self)->len); }
|
|
579
|
+
|
|
580
|
+
static VALUE builder_depth(VALUE self) { return LONG2NUM((long)get_builder(self)->depth); }
|
|
581
|
+
|
|
582
|
+
/* -- primitives: one method per code -- */
|
|
583
|
+
|
|
584
|
+
/* A reference path: one to three non-empty names, as c0_bytes backed by *tmp. */
|
|
585
|
+
static c0_bytes *path_arg(VALUE path, VALUE *tmp, long *count) {
|
|
586
|
+
c0_bytes *arr;
|
|
587
|
+
long i;
|
|
588
|
+
if (RARRAY_LEN(path) < 1 || RARRAY_LEN(path) > 3) {
|
|
589
|
+
rb_raise(eBuildError, "a reference is a group name or a path of two or three segments");
|
|
590
|
+
}
|
|
591
|
+
arr = bytes_array(path, 1, "reference segment", tmp, count);
|
|
592
|
+
for (i = 0; i < *count; i++) {
|
|
593
|
+
if (arr[i].len == 0) rb_raise(eBuildError, "reference segment must not be empty");
|
|
594
|
+
}
|
|
595
|
+
return arr;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* fs(name): FS and a file name; ends any open record. */
|
|
599
|
+
static VALUE builder_fs(VALUE self, VALUE name) {
|
|
600
|
+
c0_builder *b = get_builder(self);
|
|
601
|
+
c0_bytes n = name_arg(name, "file name");
|
|
602
|
+
c0_builder_fs(b, n.ptr, n.len);
|
|
603
|
+
builder_check(b);
|
|
604
|
+
return self;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/* gs(name = nil): GS and, when given, a group name. gs.gs("sub") is depth 2. */
|
|
608
|
+
static VALUE builder_gs(int argc, VALUE *argv, VALUE self) {
|
|
609
|
+
c0_builder *b = get_builder(self);
|
|
610
|
+
VALUE name;
|
|
611
|
+
rb_scan_args(argc, argv, "01", &name);
|
|
612
|
+
if (NIL_P(name)) {
|
|
613
|
+
c0_builder_gs(b);
|
|
614
|
+
} else {
|
|
615
|
+
c0_bytes n = name_arg(name, "group name");
|
|
616
|
+
c0_builder_gs_name(b, n.ptr, n.len);
|
|
617
|
+
}
|
|
618
|
+
builder_check(b);
|
|
619
|
+
return self;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/* rs(value = nil): RS and, when given, the record's first value. */
|
|
623
|
+
static VALUE builder_rs(int argc, VALUE *argv, VALUE self) {
|
|
624
|
+
c0_builder *b = get_builder(self);
|
|
625
|
+
VALUE value;
|
|
626
|
+
rb_scan_args(argc, argv, "01", &value);
|
|
627
|
+
if (NIL_P(value)) {
|
|
628
|
+
c0_builder_rs(b);
|
|
629
|
+
} else {
|
|
630
|
+
c0_bytes v = bytes_arg(value);
|
|
631
|
+
c0_builder_rs_value(b, v.ptr, v.len);
|
|
632
|
+
}
|
|
633
|
+
builder_check(b);
|
|
634
|
+
return self;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/* us(value = nil): US and, when given, the value that follows it. */
|
|
638
|
+
static VALUE builder_us(int argc, VALUE *argv, VALUE self) {
|
|
639
|
+
c0_builder *b = get_builder(self);
|
|
640
|
+
VALUE value;
|
|
641
|
+
rb_scan_args(argc, argv, "01", &value);
|
|
642
|
+
if (NIL_P(value)) {
|
|
643
|
+
c0_builder_us(b);
|
|
644
|
+
} else {
|
|
645
|
+
c0_bytes v = bytes_arg(value);
|
|
646
|
+
c0_builder_us_value(b, v.ptr, v.len);
|
|
647
|
+
}
|
|
648
|
+
builder_check(b);
|
|
649
|
+
return self;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/* sub(value = nil): SUB and, when given, the new side of a substitution. C0-DIFF
|
|
653
|
+
* only: rs("old").sub("new") is a replace-all; in a data body SUB is an error.
|
|
654
|
+
* Does not count as a unit. */
|
|
655
|
+
static VALUE builder_sub(int argc, VALUE *argv, VALUE self) {
|
|
656
|
+
c0_builder *b = get_builder(self);
|
|
657
|
+
VALUE value;
|
|
658
|
+
rb_scan_args(argc, argv, "01", &value);
|
|
659
|
+
if (NIL_P(value)) {
|
|
660
|
+
c0_builder_sub(b);
|
|
661
|
+
} else {
|
|
662
|
+
c0_bytes v = bytes_arg(value);
|
|
663
|
+
c0_builder_sub_value(b, v.ptr, v.len);
|
|
664
|
+
}
|
|
665
|
+
builder_check(b);
|
|
666
|
+
return self;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* soh(name = nil): SOH and, when given, a key. Bare before rs it opens a header row. */
|
|
670
|
+
static VALUE builder_soh(int argc, VALUE *argv, VALUE self) {
|
|
671
|
+
c0_builder *b = get_builder(self);
|
|
672
|
+
VALUE name;
|
|
673
|
+
rb_scan_args(argc, argv, "01", &name);
|
|
674
|
+
if (NIL_P(name)) {
|
|
675
|
+
c0_builder_soh(b);
|
|
676
|
+
} else {
|
|
677
|
+
c0_bytes n = name_arg(name, "key");
|
|
678
|
+
c0_builder_soh_name(b, n.ptr, n.len);
|
|
679
|
+
}
|
|
680
|
+
builder_check(b);
|
|
681
|
+
return self;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/* dle: open a nested level (the record state is saved). */
|
|
685
|
+
static VALUE builder_dle(VALUE self) {
|
|
686
|
+
c0_builder *b = get_builder(self);
|
|
687
|
+
c0_builder_dle(b);
|
|
688
|
+
builder_check(b);
|
|
689
|
+
return self;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* can: close the innermost level; C0::BuildError if none is open. */
|
|
693
|
+
static VALUE builder_can(VALUE self) {
|
|
694
|
+
c0_builder *b = get_builder(self);
|
|
695
|
+
if (b->depth == 0) rb_raise(eBuildError, "CAN with no open level");
|
|
696
|
+
c0_builder_can(b);
|
|
697
|
+
builder_check(b);
|
|
698
|
+
return self;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* bel(text = nil): BEL and, when given, a note. Bare, it marks the element that follows dead. */
|
|
702
|
+
static VALUE builder_bel(int argc, VALUE *argv, VALUE self) {
|
|
703
|
+
c0_builder *b = get_builder(self);
|
|
704
|
+
VALUE text;
|
|
705
|
+
rb_scan_args(argc, argv, "01", &text);
|
|
706
|
+
if (NIL_P(text)) {
|
|
707
|
+
c0_builder_bel(b);
|
|
708
|
+
} else {
|
|
709
|
+
c0_bytes t = bytes_arg(text);
|
|
710
|
+
if (t.len == 0) rb_raise(rb_eArgError, "a note needs content: bare BEL marks the next element dead");
|
|
711
|
+
c0_builder_bel_text(b, t.ptr, t.len);
|
|
712
|
+
}
|
|
713
|
+
builder_check(b);
|
|
714
|
+
return self;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/* enq(name) or enq(group, record[, field]): ENQ and a reference. */
|
|
718
|
+
static VALUE builder_enq(int argc, VALUE *argv, VALUE self) {
|
|
719
|
+
c0_builder *b = get_builder(self);
|
|
720
|
+
VALUE tmp;
|
|
721
|
+
long n;
|
|
722
|
+
c0_bytes *arr = path_arg(splat_or_array(argc, argv), &tmp, &n);
|
|
723
|
+
c0_builder_enq_path(b, arr, (size_t)n);
|
|
724
|
+
RB_GC_GUARD(tmp);
|
|
725
|
+
builder_check(b);
|
|
726
|
+
return self;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/* -- documents -- */
|
|
730
|
+
|
|
731
|
+
static VALUE builder_key_pair(VALUE key, VALUE val, VALUE self);
|
|
732
|
+
|
|
733
|
+
static int syn_pair_i(VALUE key, VALUE val, VALUE self) {
|
|
734
|
+
builder_key_pair(key, val, self);
|
|
735
|
+
return ST_CONTINUE;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/* syn(pairs = {}, **pairs): SYN and, when given, the heading's keyed units in order. */
|
|
739
|
+
static VALUE builder_syn(int argc, VALUE *argv, VALUE self) {
|
|
740
|
+
c0_builder *b = get_builder(self);
|
|
741
|
+
VALUE pairs, kw;
|
|
742
|
+
rb_scan_args(argc, argv, "01:", &pairs, &kw);
|
|
743
|
+
c0_builder_syn(b);
|
|
744
|
+
builder_check(b);
|
|
745
|
+
if (!NIL_P(pairs)) rb_hash_foreach(rb_convert_type(pairs, T_HASH, "Hash", "to_hash"), syn_pair_i, self);
|
|
746
|
+
if (!NIL_P(kw)) rb_hash_foreach(kw, syn_pair_i, self);
|
|
747
|
+
return self;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
static VALUE builder_eot(VALUE self) {
|
|
751
|
+
c0_builder *b = get_builder(self);
|
|
752
|
+
c0_builder_eot(b);
|
|
753
|
+
builder_check(b);
|
|
754
|
+
return self;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/* etb(payload = nil): a commit, with an optional plain payload. */
|
|
758
|
+
static VALUE builder_etb(int argc, VALUE *argv, VALUE self) {
|
|
759
|
+
c0_builder *b = get_builder(self);
|
|
760
|
+
VALUE payload;
|
|
761
|
+
rb_scan_args(argc, argv, "01", &payload);
|
|
762
|
+
if (NIL_P(payload)) {
|
|
763
|
+
c0_builder_etb(b);
|
|
764
|
+
} else {
|
|
765
|
+
c0_bytes p = name_arg(payload, "ETB payload");
|
|
766
|
+
c0_builder_etb_payload(b, p.ptr, p.len);
|
|
767
|
+
}
|
|
768
|
+
builder_check(b);
|
|
769
|
+
return self;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/* em(tail): EM and the raw tail; nothing may follow. */
|
|
773
|
+
static VALUE builder_em(VALUE self, VALUE tail) {
|
|
774
|
+
c0_builder *b = get_builder(self);
|
|
775
|
+
c0_bytes t = bytes_arg(tail);
|
|
776
|
+
c0_builder_em(b, t.ptr, t.len);
|
|
777
|
+
builder_check(b);
|
|
778
|
+
return self;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/* -- structure -- */
|
|
782
|
+
|
|
783
|
+
/* file(name) { ... } */
|
|
784
|
+
static VALUE builder_file(VALUE self, VALUE name) {
|
|
785
|
+
c0_builder *b = get_builder(self);
|
|
786
|
+
c0_bytes n = name_arg(name, "file name");
|
|
787
|
+
c0_build_file(b, n.ptr, n.len);
|
|
788
|
+
builder_check(b);
|
|
789
|
+
if (rb_block_given_p()) rb_yield(self);
|
|
790
|
+
return self;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
static VALUE builder_header_ary(VALUE self, VALUE names) {
|
|
794
|
+
c0_builder *b = get_builder(self);
|
|
795
|
+
VALUE tmp;
|
|
796
|
+
long n;
|
|
797
|
+
c0_bytes *arr = bytes_array(names, 1, "header name", &tmp, &n);
|
|
798
|
+
c0_build_header(b, arr, (size_t)n);
|
|
799
|
+
RB_GC_GUARD(tmp);
|
|
800
|
+
builder_check(b);
|
|
801
|
+
return self;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* group(name, headers = nil, depth = 1) { ... }: GS×depth name, then a header row when given. */
|
|
805
|
+
static VALUE builder_group(int argc, VALUE *argv, VALUE self) {
|
|
806
|
+
c0_builder *b = get_builder(self);
|
|
807
|
+
VALUE name, headers, depth;
|
|
808
|
+
c0_bytes n;
|
|
809
|
+
long d;
|
|
810
|
+
rb_scan_args(argc, argv, "12", &name, &headers, &depth);
|
|
811
|
+
d = NIL_P(depth) ? 1 : NUM2LONG(depth);
|
|
812
|
+
if (d < 1) rb_raise(rb_eArgError, "depth must be at least 1");
|
|
813
|
+
n = name_arg(name, "group name");
|
|
814
|
+
c0_build_section(b, n.ptr, n.len, (size_t)d);
|
|
815
|
+
builder_check(b);
|
|
816
|
+
if (!NIL_P(headers)) builder_header_ary(self, rb_convert_type(headers, T_ARRAY, "Array", "to_ary"));
|
|
817
|
+
if (rb_block_given_p()) rb_yield(self);
|
|
818
|
+
return self;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/* section(name, depth = 1) { ... }: document mode, a heading at depth. */
|
|
822
|
+
static VALUE builder_section(int argc, VALUE *argv, VALUE self) {
|
|
823
|
+
VALUE name, depth, args[3];
|
|
824
|
+
rb_scan_args(argc, argv, "11", &name, &depth);
|
|
825
|
+
args[0] = name;
|
|
826
|
+
args[1] = Qnil;
|
|
827
|
+
args[2] = NIL_P(depth) ? INT2FIX(1) : depth;
|
|
828
|
+
return builder_group(3, args, self);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/* header(*names) or header(names): SOH RS name US name … */
|
|
832
|
+
static VALUE builder_header(int argc, VALUE *argv, VALUE self) {
|
|
833
|
+
return builder_header_ary(self, splat_or_array(argc, argv));
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/* -- records -- */
|
|
837
|
+
|
|
838
|
+
/* record(*fields) or record(fields): RS and the units; record() opens an empty record. */
|
|
839
|
+
static VALUE builder_record(int argc, VALUE *argv, VALUE self) {
|
|
840
|
+
c0_builder *b = get_builder(self);
|
|
841
|
+
VALUE fields = splat_or_array(argc, argv);
|
|
842
|
+
VALUE tmp;
|
|
843
|
+
long n;
|
|
844
|
+
c0_bytes *arr = bytes_array(fields, 0, "field", &tmp, &n);
|
|
845
|
+
c0_build_record(b, arr, (size_t)n);
|
|
846
|
+
RB_GC_GUARD(tmp);
|
|
847
|
+
builder_check(b);
|
|
848
|
+
return self;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* field(value): the next unit of the open record (opens one if none). */
|
|
852
|
+
static VALUE builder_field(VALUE self, VALUE value) {
|
|
853
|
+
c0_builder *b = get_builder(self);
|
|
854
|
+
c0_bytes v = bytes_arg(value);
|
|
855
|
+
c0_build_field(b, v.ptr, v.len);
|
|
856
|
+
builder_check(b);
|
|
857
|
+
return self;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
static VALUE builder_block(VALUE self, VALUE text) {
|
|
861
|
+
c0_builder *b = get_builder(self);
|
|
862
|
+
c0_bytes v = bytes_arg(text);
|
|
863
|
+
c0_build_block(b, v.ptr, v.len);
|
|
864
|
+
builder_check(b);
|
|
865
|
+
return self;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
static VALUE builder_item(VALUE self, VALUE text) {
|
|
869
|
+
c0_builder *b = get_builder(self);
|
|
870
|
+
c0_bytes v = bytes_arg(text);
|
|
871
|
+
c0_build_item(b, v.ptr, v.len);
|
|
872
|
+
builder_check(b);
|
|
873
|
+
return self;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
static VALUE level_close_ensure(VALUE self) {
|
|
877
|
+
c0_builder *b = get_builder(self);
|
|
878
|
+
if (b->depth > 0) c0_build_level_close(b);
|
|
879
|
+
return Qnil;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
static VALUE yield_self(VALUE self) { return rb_yield(self); }
|
|
883
|
+
|
|
884
|
+
/* Open a level, run the block, close the level (even on a raise). */
|
|
885
|
+
static VALUE builder_in_level(VALUE self) {
|
|
886
|
+
c0_builder *b = get_builder(self);
|
|
887
|
+
builder_check(b);
|
|
888
|
+
if (rb_block_given_p()) rb_ensure(yield_self, self, level_close_ensure, self);
|
|
889
|
+
else level_close_ensure(self);
|
|
890
|
+
builder_check(b);
|
|
891
|
+
return self;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/* One keyed unit: SOH key US value, in the open record or the heading. */
|
|
895
|
+
static VALUE builder_key_pair(VALUE key, VALUE val, VALUE self) {
|
|
896
|
+
c0_builder *b = get_builder(self);
|
|
897
|
+
c0_bytes k = name_arg(key, "key"), v = bytes_arg(val);
|
|
898
|
+
c0_build_key(b, k.ptr, k.len, v.ptr, v.len);
|
|
899
|
+
builder_check(b);
|
|
900
|
+
return self;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/* key(name, value) or key(name) { ... }: a keyed unit, the block form writing a nested level as its value. */
|
|
904
|
+
static VALUE builder_key(int argc, VALUE *argv, VALUE self) {
|
|
905
|
+
c0_builder *b = get_builder(self);
|
|
906
|
+
VALUE name, value;
|
|
907
|
+
rb_scan_args(argc, argv, "11", &name, &value);
|
|
908
|
+
if (argc == 2) return builder_key_pair(name, value, self);
|
|
909
|
+
if (!rb_block_given_p()) rb_raise(rb_eArgError, "key needs a value or a block");
|
|
910
|
+
{
|
|
911
|
+
c0_bytes k = name_arg(name, "key");
|
|
912
|
+
c0_build_key_level_open(b, k.ptr, k.len);
|
|
913
|
+
}
|
|
914
|
+
return builder_in_level(self);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/* heading_level(key) { ... }: a heading key whose value is a level (the blob table). */
|
|
918
|
+
static VALUE builder_heading_level(VALUE self, VALUE key) {
|
|
919
|
+
VALUE args[1];
|
|
920
|
+
args[0] = key;
|
|
921
|
+
return builder_key(1, args, self);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
static int map_pair_i(VALUE key, VALUE val, VALUE self) {
|
|
925
|
+
builder_key_pair(key, val, self);
|
|
926
|
+
return ST_CONTINUE;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/* map(pairs): a keyed record, entries in the order given. */
|
|
930
|
+
static VALUE builder_map(VALUE self, VALUE pairs) {
|
|
931
|
+
c0_builder *b = get_builder(self);
|
|
932
|
+
c0_build_record(b, NULL, 0);
|
|
933
|
+
builder_check(b);
|
|
934
|
+
rb_hash_foreach(rb_convert_type(pairs, T_HASH, "Hash", "to_hash"), map_pair_i, self);
|
|
935
|
+
return self;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/* level { ... }: a nested level as the next unit; inside, the builder is at the top of a fresh body. */
|
|
939
|
+
static VALUE builder_level(VALUE self) {
|
|
940
|
+
c0_builder *b = get_builder(self);
|
|
941
|
+
c0_build_level_open(b);
|
|
942
|
+
return builder_in_level(self);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/* list(items) or list(*items): a flat list as the next unit. */
|
|
946
|
+
static VALUE builder_list(int argc, VALUE *argv, VALUE self) {
|
|
947
|
+
c0_builder *b = get_builder(self);
|
|
948
|
+
VALUE items = splat_or_array(argc, argv);
|
|
949
|
+
VALUE tmp;
|
|
950
|
+
long n;
|
|
951
|
+
c0_bytes *arr = bytes_array(items, 0, "item", &tmp, &n);
|
|
952
|
+
c0_build_list(b, arr, (size_t)n);
|
|
953
|
+
RB_GC_GUARD(tmp);
|
|
954
|
+
builder_check(b);
|
|
955
|
+
return self;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/* ref(name) or ref(group, record[, field]): a reference as the next unit. */
|
|
959
|
+
static VALUE builder_ref(int argc, VALUE *argv, VALUE self) {
|
|
960
|
+
c0_builder *b = get_builder(self);
|
|
961
|
+
VALUE tmp;
|
|
962
|
+
long n;
|
|
963
|
+
c0_bytes *arr = path_arg(splat_or_array(argc, argv), &tmp, &n);
|
|
964
|
+
c0_build_ref_path(b, arr, (size_t)n);
|
|
965
|
+
RB_GC_GUARD(tmp);
|
|
966
|
+
builder_check(b);
|
|
967
|
+
return self;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* note(text): BEL and a note (plain, or text when it must be). */
|
|
971
|
+
static VALUE builder_note(VALUE self, VALUE text) {
|
|
972
|
+
c0_builder *b = get_builder(self);
|
|
973
|
+
c0_bytes t = bytes_arg(text);
|
|
974
|
+
if (t.len == 0) rb_raise(rb_eArgError, "a note needs content: BEL before a code marks it dead");
|
|
975
|
+
c0_build_note(b, t.ptr, t.len);
|
|
976
|
+
builder_check(b);
|
|
977
|
+
return self;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/* ---- init --------------------------------------------------------------- */
|
|
981
|
+
|
|
982
|
+
void Init_c0_ext(void) {
|
|
983
|
+
mC0 = rb_define_module("C0");
|
|
984
|
+
mExt = rb_define_module_under(mC0, "Ext");
|
|
985
|
+
mPretty = rb_define_module_under(mC0, "Pretty");
|
|
986
|
+
|
|
987
|
+
eError = rb_define_class_under(mC0, "Error", rb_eStandardError);
|
|
988
|
+
eSyntaxError = rb_define_class_under(mC0, "SyntaxError", eError);
|
|
989
|
+
rb_define_attr(eSyntaxError, "position", 1, 0);
|
|
990
|
+
eUnassignedCodeError = rb_define_class_under(mC0, "UnassignedCodeError", eSyntaxError);
|
|
991
|
+
eUnexpectedEndError = rb_define_class_under(mC0, "UnexpectedEndError", eSyntaxError);
|
|
992
|
+
eKeyError = rb_define_class_under(mC0, "KeyError", eError);
|
|
993
|
+
eBuildError = rb_define_class_under(mC0, "BuildError", eError);
|
|
994
|
+
eGlyphInDataError = rb_define_class_under(mPretty, "GlyphInDataError", eError);
|
|
995
|
+
|
|
996
|
+
rb_define_const(mC0, "VERSION", rb_str_freeze(rb_utf8_str_new_cstr(C0_VERSION)));
|
|
997
|
+
|
|
998
|
+
rb_define_module_function(mExt, "is_assigned", ext_is_assigned, 1);
|
|
999
|
+
rb_define_module_function(mExt, "is_layout", ext_is_layout, 1);
|
|
1000
|
+
rb_define_module_function(mExt, "is_plain", ext_is_plain, 1);
|
|
1001
|
+
rb_define_module_function(mExt, "trim", ext_trim, 1);
|
|
1002
|
+
rb_define_module_function(mExt, "decode_text", ext_decode_text, 1);
|
|
1003
|
+
rb_define_module_function(mExt, "validate", ext_validate, 1);
|
|
1004
|
+
rb_define_module_function(mExt, "canonical", ext_canonical, 1);
|
|
1005
|
+
rb_define_module_function(mExt, "unit_type", ext_unit_type, 3);
|
|
1006
|
+
rb_define_module_function(mExt, "value", ext_value, 3);
|
|
1007
|
+
rb_define_module_function(mExt, "unit_level", ext_unit_level, 3);
|
|
1008
|
+
rb_define_module_function(mExt, "unit_ref", ext_unit_ref, 3);
|
|
1009
|
+
rb_define_module_function(mExt, "unit_list", ext_unit_list, 3);
|
|
1010
|
+
rb_define_module_function(mExt, "record_keyed", ext_record_keyed, 3);
|
|
1011
|
+
rb_define_module_function(mExt, "record_units", ext_record_units, 3);
|
|
1012
|
+
rb_define_module_function(mExt, "record_pairs", ext_record_pairs, 3);
|
|
1013
|
+
rb_define_module_function(mExt, "files", ext_files, 3);
|
|
1014
|
+
rb_define_module_function(mExt, "groups", ext_groups, 3);
|
|
1015
|
+
rb_define_module_function(mExt, "group_header", ext_group_header, 3);
|
|
1016
|
+
rb_define_module_function(mExt, "group_records", ext_group_records, 3);
|
|
1017
|
+
rb_define_module_function(mExt, "body_bare", ext_body_bare, 3);
|
|
1018
|
+
rb_define_module_function(mExt, "document", ext_document, 2);
|
|
1019
|
+
rb_define_module_function(mExt, "documents", ext_documents, 1);
|
|
1020
|
+
rb_define_module_function(mExt, "stream", ext_stream, 1);
|
|
1021
|
+
rb_define_module_function(mExt, "stream_undamaged", ext_stream_undamaged, 1);
|
|
1022
|
+
rb_define_module_function(mExt, "last_commit_end", ext_last_commit_end, 1);
|
|
1023
|
+
rb_define_module_function(mExt, "pretty_format", ext_pretty_format, 3);
|
|
1024
|
+
rb_define_module_function(mExt, "pretty_parse", ext_pretty_parse, 1);
|
|
1025
|
+
|
|
1026
|
+
cBuilder = rb_define_class_under(mC0, "Builder", rb_cObject);
|
|
1027
|
+
rb_define_alloc_func(cBuilder, builder_alloc);
|
|
1028
|
+
rb_define_method(cBuilder, "initialize", builder_initialize, 0);
|
|
1029
|
+
rb_define_method(cBuilder, "bytes", builder_bytes, 0);
|
|
1030
|
+
rb_define_method(cBuilder, "bytesize", builder_size, 0);
|
|
1031
|
+
rb_define_method(cBuilder, "depth", builder_depth, 0);
|
|
1032
|
+
rb_define_method(cBuilder, "fs", builder_fs, 1);
|
|
1033
|
+
rb_define_method(cBuilder, "gs", builder_gs, -1);
|
|
1034
|
+
rb_define_method(cBuilder, "rs", builder_rs, -1);
|
|
1035
|
+
rb_define_method(cBuilder, "us", builder_us, -1);
|
|
1036
|
+
rb_define_method(cBuilder, "sub", builder_sub, -1);
|
|
1037
|
+
rb_define_method(cBuilder, "soh", builder_soh, -1);
|
|
1038
|
+
rb_define_method(cBuilder, "dle", builder_dle, 0);
|
|
1039
|
+
rb_define_method(cBuilder, "can", builder_can, 0);
|
|
1040
|
+
rb_define_method(cBuilder, "bel", builder_bel, -1);
|
|
1041
|
+
rb_define_method(cBuilder, "enq", builder_enq, -1);
|
|
1042
|
+
rb_define_method(cBuilder, "syn", builder_syn, -1);
|
|
1043
|
+
rb_define_method(cBuilder, "heading_level", builder_heading_level, 1);
|
|
1044
|
+
rb_define_method(cBuilder, "eot", builder_eot, 0);
|
|
1045
|
+
rb_define_method(cBuilder, "etb", builder_etb, -1);
|
|
1046
|
+
rb_define_method(cBuilder, "em", builder_em, 1);
|
|
1047
|
+
rb_define_method(cBuilder, "file", builder_file, 1);
|
|
1048
|
+
rb_define_method(cBuilder, "group", builder_group, -1);
|
|
1049
|
+
rb_define_method(cBuilder, "section", builder_section, -1);
|
|
1050
|
+
rb_define_method(cBuilder, "header", builder_header, -1);
|
|
1051
|
+
rb_define_method(cBuilder, "record", builder_record, -1);
|
|
1052
|
+
rb_define_method(cBuilder, "field", builder_field, 1);
|
|
1053
|
+
rb_define_method(cBuilder, "block", builder_block, 1);
|
|
1054
|
+
rb_define_method(cBuilder, "item", builder_item, 1);
|
|
1055
|
+
rb_define_method(cBuilder, "map", builder_map, 1);
|
|
1056
|
+
rb_define_method(cBuilder, "key", builder_key, -1);
|
|
1057
|
+
rb_define_method(cBuilder, "level", builder_level, 0);
|
|
1058
|
+
rb_define_method(cBuilder, "list", builder_list, -1);
|
|
1059
|
+
rb_define_method(cBuilder, "ref", builder_ref, -1);
|
|
1060
|
+
rb_define_method(cBuilder, "note", builder_note, 1);
|
|
1061
|
+
}
|