Line data Source code
1 : #include "fd_types_yaml.h"
2 : #include "fd_types_meta.h"
3 : #include "../../ballet/base58/fd_base58.h"
4 : #include "../../ballet/hex/fd_hex.h"
5 :
6 : #include <ctype.h>
7 : #include <stdio.h>
8 : #include <stdlib.h>
9 :
10 : #define FD_FLAMENCO_YAML_INDENT_BUFSZ (2UL*FD_FLAMENCO_YAML_MAX_INDENT+1UL)
11 :
12 : /* STATE_{...} identify the state of the YAML writer. This is used
13 : because assembling the YAML stream requires different combinations
14 : of print operations depending on the sequence of AST nodes. */
15 :
16 : #define STATE_NULL (0) /* Sentinel value */
17 2025 : #define STATE_OBJECT_BEGIN (2) /* Writing object, ==0 elems so far */
18 2460 : #define STATE_OBJECT (3) /* Writing object, >0 elems so far */
19 141 : #define STATE_ARRAY_BEGIN (4) /* Writing array, ==0 elems so far */
20 561 : #define STATE_ARRAY (5) /* Writing array, >0 elems so far */
21 : #define STATE_OPTION_BEGIN (6) /* Writing nullable, waiting for elem */
22 :
23 : /* fd_flamenco_yaml provides methods for converting a bincode-like AST of
24 : nodes into a YAML text stream.
25 :
26 : indent is a string containing the prefix suitable for the current
27 : indent level. indent_stack[ i ] is the number of chars in indent
28 : level i.
29 :
30 : For example, the following structure
31 :
32 : my_object:
33 : key0: 34
34 : key1:
35 : - 128
36 : - 129
37 : key2: true
38 : key3: null
39 :
40 : Results in the following walk:
41 :
42 : [LEVEL] [TYPE] [NAME] [VALUE]
43 : 0 MAP
44 : 1 MAP my_object
45 : 2 SINT key0 34
46 : 2 ARR key1
47 : 3 SINT 128
48 : 3 SINT 129
49 : 3 ARR_END
50 : 2 BOOL key2 true
51 : 2 MAP_END
52 : 2 OPT key3
53 : 3 OPT_END
54 : 1 MAP_END
55 : 0 MAP_END
56 :
57 : After the start node of a collection types (arrays, maps, options),
58 : the walk level may increment. The subsequent nodes in this
59 : incremented level then belong to the collection. The last node in
60 : the incremented level is always the collection's corresponding end
61 : node.
62 :
63 : Finally, we support option types. During walk, these are presented
64 : as a separate */
65 :
66 : fd_flamenco_yaml_t *
67 39 : fd_flamenco_yaml_new( void * mem ) {
68 :
69 39 : if( FD_UNLIKELY( !mem ) ) {
70 0 : FD_LOG_WARNING(( "NULL mem" ));
71 0 : return NULL;
72 0 : }
73 :
74 39 : fd_flamenco_yaml_t * yaml = (fd_flamenco_yaml_t *)mem;
75 39 : memset( yaml, 0, sizeof(*yaml) );
76 39 : memset( yaml->indent, ' ', sizeof(yaml->indent) );
77 39 : return (fd_flamenco_yaml_t *)mem;
78 39 : }
79 :
80 : void *
81 24 : fd_flamenco_yaml_delete( fd_flamenco_yaml_t * yaml ) {
82 24 : return yaml;
83 24 : }
84 :
85 : fd_flamenco_yaml_t *
86 : fd_flamenco_yaml_init( fd_flamenco_yaml_t * self,
87 39 : void * _file ) {
88 :
89 39 : if( FD_UNLIKELY( !self ) ) {
90 0 : FD_LOG_WARNING(( "NULL self" ));
91 0 : return NULL;
92 0 : }
93 39 : if( FD_UNLIKELY( !_file ) ) {
94 0 : FD_LOG_WARNING(( "NULL file" ));
95 0 : return NULL;
96 0 : }
97 :
98 39 : self->file = _file;
99 :
100 39 : return self;
101 39 : }
102 :
103 : void *
104 0 : fd_flamenco_yaml_file( fd_flamenco_yaml_t * self ) {
105 0 : return self->file;
106 0 : }
107 :
108 : /* fd_flamenco_yaml_walk iteratively serializes YAML while keeping
109 : minimal state.
110 :
111 : Throughout this function, serialization state is illustrated using
112 : code comments. The '$' symbol symbolizes the current stream cursor. */
113 :
114 : void
115 : fd_flamenco_yaml_walk( void * _self,
116 : void const * arg,
117 : char const * name,
118 : int type,
119 : char const * type_name,
120 : uint level,
121 3081 : uint varint ) {
122 3081 : (void)type_name;
123 3081 : (void)varint;
124 :
125 3081 : if( level>=FD_FLAMENCO_YAML_MAX_INDENT-1 ) {
126 0 : FD_LOG_WARNING(( "indent level %u exceeds max %lu",
127 0 : level, FD_FLAMENCO_YAML_MAX_INDENT ));
128 0 : return;
129 0 : }
130 :
131 3081 : if( type == FD_FLAMENCO_TYPE_ENUM_DISC ) {
132 : /* Don't do anything with this */
133 15 : return;
134 15 : }
135 :
136 3066 : fd_flamenco_yaml_t * self = (fd_flamenco_yaml_t *)_self;
137 3066 : FILE * file = self->file;
138 :
139 : /* On entry, there are either two cursor states:
140 :
141 : At the beginning of a line, if there is at least one predecessor
142 : in the current collection:
143 :
144 : ...
145 : object:
146 : - foo
147 : - bar
148 : $
149 : ...
150 :
151 : Or, at the beginning of the line, if we are serializing the first
152 : element. We handle this as a special case, because we don't know
153 : whether the subsequent content can be printed inline, or needs a
154 : new line.
155 :
156 : ...
157 : object: $
158 : ...
159 :
160 : For example, an empty array is the following:
161 :
162 : ...
163 : object: []
164 : ... */
165 :
166 : /* Check if we are at the beginning of a collection */
167 3066 : if( (self->stack[ level ] & 1)==0 ) {
168 :
169 : /* Collection is empty -- print inline */
170 765 : if( fd_flamenco_type_is_collection_end( type ) ) {
171 6 : if( name )
172 6 : fprintf( file, "%s: ", name );
173 :
174 6 : switch( type ) {
175 0 : case FD_FLAMENCO_TYPE_MAP_END:
176 0 : case FD_FLAMENCO_TYPE_ENUM_END:
177 0 : fprintf( file, "{}\n" );
178 0 : break;
179 6 : case FD_FLAMENCO_TYPE_ARR_END:
180 6 : fprintf( file, "[]\n" );
181 6 : break;
182 6 : }
183 :
184 6 : return;
185 6 : }
186 :
187 : /* Check if we should split off into a separate line */
188 759 : int split = 0;
189 759 : switch( self->stack[ level ] ) {
190 675 : case STATE_OBJECT_BEGIN:
191 : /* Objects nested in objects go on a separate line:
192 :
193 : ...
194 : a: <---
195 : b: <---
196 : c: {}
197 : ... */
198 :
199 675 : split = ( level>1 )
200 675 : && ( ( (self->stack[ level-1 ])==STATE_OBJECT ) );
201 675 : break;
202 45 : case STATE_ARRAY_BEGIN:
203 : /* Arrays nested in arrays or objects go on a separate line:
204 :
205 : ... | ...
206 : - <--- | a: <---
207 : - <--- | - a: 3
208 : - [] | b: 4
209 : ... | ... */
210 :
211 45 : split = ( level>1 )
212 45 : && ( ( (self->stack[ level-1 ])==STATE_OBJECT )
213 36 : | ( (self->stack[ level-1 ])==STATE_ARRAY ) );
214 45 : break;
215 759 : }
216 :
217 759 : if( split ) {
218 186 : fprintf( file, "\n" );
219 186 : fwrite( self->indent, 2, (ulong)level-1, file );
220 186 : }
221 :
222 2301 : } else {
223 :
224 : /* Nothing to do if collection ends, but at least one item printed */
225 :
226 2301 : if( fd_flamenco_type_is_collection_end( type ) )
227 720 : return;
228 :
229 : /* We are at the beginning of a line.
230 :
231 : Indent according to current level.
232 : If just started an object or array, inhibit indent. */
233 :
234 1581 : long indent = (long)level-1L;
235 1581 : fwrite( self->indent, 2, (ulong)indent, file );
236 :
237 1581 : }
238 :
239 : /* Print node tag */
240 2340 : switch( self->stack[ level ] ) {
241 675 : case STATE_OBJECT_BEGIN:
242 1776 : case STATE_OBJECT:
243 1776 : fprintf( file, "%s: ", name );
244 1776 : break;
245 :
246 45 : case STATE_ARRAY_BEGIN:
247 525 : case STATE_ARRAY:
248 525 : fprintf( file, "- " );
249 525 : break;
250 2340 : }
251 :
252 : /* Print node value */
253 2340 : switch( type ) {
254 660 : case FD_FLAMENCO_TYPE_MAP:
255 675 : case FD_FLAMENCO_TYPE_ENUM:
256 675 : self->stack[ level+1 ] = STATE_OBJECT_BEGIN;
257 675 : break;
258 51 : case FD_FLAMENCO_TYPE_ARR:
259 51 : self->stack[ level+1 ] = STATE_ARRAY_BEGIN;
260 51 : break;
261 :
262 3 : case FD_FLAMENCO_TYPE_NULL:
263 3 : fprintf( file, "null\n" );
264 3 : break;
265 6 : case FD_FLAMENCO_TYPE_BOOL:
266 6 : fprintf( file, "%s\n", (*(uchar const *)arg) ? "true" : "false" );
267 6 : break;
268 102 : case FD_FLAMENCO_TYPE_UCHAR:
269 102 : fprintf( file, "%u\n", *(uchar const *)arg );
270 102 : break;
271 0 : case FD_FLAMENCO_TYPE_SCHAR:
272 0 : fprintf( file, "%d\n", *(schar const *)arg );
273 0 : break;
274 0 : case FD_FLAMENCO_TYPE_USHORT:
275 0 : fprintf( file, "%u\n", *(ushort const *)arg );
276 0 : break;
277 0 : case FD_FLAMENCO_TYPE_SSHORT:
278 0 : fprintf( file, "%d\n", *(short const *)arg );
279 0 : break;
280 135 : case FD_FLAMENCO_TYPE_UINT:
281 135 : fprintf( file, "%u\n", *(uint const *)arg );
282 135 : break;
283 0 : case FD_FLAMENCO_TYPE_SINT:
284 0 : fprintf( file, "%d\n", *(int const *)arg );
285 0 : break;
286 1125 : case FD_FLAMENCO_TYPE_ULONG:
287 1125 : fprintf( file, "%lu\n", *(ulong const *)arg );
288 1125 : break;
289 6 : case FD_FLAMENCO_TYPE_SLONG:
290 6 : fprintf( file, "%ld\n", *(long const *)arg );
291 6 : break;
292 0 : # if FD_HAS_INT128
293 0 : case FD_FLAMENCO_TYPE_UINT128:
294 0 : case FD_FLAMENCO_TYPE_SINT128: {
295 0 : uint128 v = *(uint128 const *)arg;
296 0 : fprintf( file, "%s: 0x%016lx%016lx\n", name,
297 0 : (ulong)(v>>64), (ulong)v );
298 0 : break;
299 0 : }
300 0 : # endif
301 0 : case FD_FLAMENCO_TYPE_FLOAT:
302 0 : fprintf( file, "%f\n", (double)( *(float const *)arg ) );
303 0 : break;
304 0 : case FD_FLAMENCO_TYPE_DOUBLE:
305 0 : fprintf( file, "%f\n", *(double const *)arg );
306 0 : break;
307 228 : case FD_FLAMENCO_TYPE_HASH256: {
308 228 : char buf[ FD_BASE58_ENCODED_32_SZ ];
309 228 : fd_base58_encode_32( arg, NULL, buf );
310 228 : fprintf( file, "'%s'\n", buf );
311 228 : break;
312 0 : }
313 0 : case FD_FLAMENCO_TYPE_HASH16384: {
314 : /* FIXME: This currently truncates the hash */
315 0 : char hex[ 256 ];
316 0 : fd_hex_encode( hex, arg, 128 );
317 0 : fprintf( file, "'%s... (truncated)'\n", hex );
318 0 : break;
319 0 : }
320 9 : case FD_FLAMENCO_TYPE_SIG512: {
321 9 : char buf[ FD_BASE58_ENCODED_64_SZ ];
322 9 : fd_base58_encode_64( arg, NULL, buf );
323 9 : fprintf( file, "'%s'\n", buf );
324 9 : break;
325 0 : }
326 0 : case FD_FLAMENCO_TYPE_CSTR:
327 0 : fprintf( file, "'%s'\n", (char const *)arg );
328 0 : break;
329 0 : case FD_FLAMENCO_TYPE_ENUM_DISC:
330 0 : break;
331 0 : default:
332 0 : FD_LOG_CRIT(( "unknown type %#x", (uint)type ));
333 0 : break;
334 2340 : }
335 :
336 : /* Remember that we processed an element in the current level */
337 2340 : self->stack[ level ] |= 1;
338 2340 : }
339 :
340 : static fd_flamenco_yaml_t * g_yaml = NULL;
341 :
342 : fd_flamenco_yaml_t *
343 0 : fd_get_types_yaml(void) {
344 0 : if (NULL != g_yaml)
345 0 : return g_yaml;
346 0 : g_yaml = fd_flamenco_yaml_init( fd_flamenco_yaml_new(malloc( fd_flamenco_yaml_footprint() ) ), stdout );
347 0 : return g_yaml;
348 0 : }
349 :
350 : void
351 0 : fd_flush_yaml_dump(void) {
352 0 : if (NULL != g_yaml)
353 0 : fflush(g_yaml->file);
354 0 : }
|