|
1 /* |
|
2 * lyaml.c, LibYAML binding for Lua |
|
3 * |
|
4 * Copyright (c) 2009, Andrew Danforth <acd@weirdness.net> |
|
5 * |
|
6 * Permission is hereby granted, free of charge, to any person obtaining a copy |
|
7 * of this software and associated documentation files (the "Software"), to deal |
|
8 * in the Software without restriction, including without limitation the rights |
|
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
10 * copies of the Software, and to permit persons to whom the Software is |
|
11 * furnished to do so, subject to the following conditions: |
|
12 * |
|
13 * The above copyright notice and this permission notice shall be included in |
|
14 * all copies or substantial portions of the Software. |
|
15 * |
|
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
22 * THE SOFTWARE. |
|
23 * |
|
24 * Portions of this software were inspired by Perl's YAML::LibYAML module by |
|
25 * Ingy döt Net <ingy@cpan.org> |
|
26 * |
|
27 */ |
|
28 |
|
29 #include <string.h> |
|
30 #include <stdlib.h> |
|
31 |
|
32 #include <lauxlib.h> |
|
33 #include <lua.h> |
|
34 #include <lualib.h> |
|
35 |
|
36 #include "yaml.h" |
|
37 #include "b64.h" |
|
38 |
|
39 /* configurable flags */ |
|
40 static char Dump_Auto_Array = 1; |
|
41 static char Dump_Error_on_Unsupported = 0; |
|
42 static char Dump_Check_Metatables = 1; |
|
43 static char Load_Set_Metatables = 1; |
|
44 static char Load_Numeric_Scalars = 1; |
|
45 static char Load_Nulls_As_Nil = 0; |
|
46 |
|
47 #define LUAYAML_TAG_PREFIX "tag:yaml.org,2002:" |
|
48 |
|
49 #define LUAYAML_KIND_UNKNOWN 0 |
|
50 #define LUAYAML_KIND_SEQUENCE 1 |
|
51 #define LUAYAML_KIND_MAPPING 2 |
|
52 |
|
53 #define RETURN_ERRMSG(s, msg) do { \ |
|
54 lua_pushstring(s->L, msg); \ |
|
55 s->error = 1; \ |
|
56 return; \ |
|
57 } while(0) |
|
58 |
|
59 struct lua_yaml_loader { |
|
60 lua_State *L; |
|
61 int anchortable_index; |
|
62 int sequencemt_index; |
|
63 int mapmt_index; |
|
64 int document_count; |
|
65 yaml_parser_t parser; |
|
66 yaml_event_t event; |
|
67 char validevent; |
|
68 char error; |
|
69 }; |
|
70 |
|
71 struct lua_yaml_dumper { |
|
72 lua_State *L; |
|
73 int anchortable_index; |
|
74 unsigned int anchor_number; |
|
75 yaml_emitter_t emitter; |
|
76 char error; |
|
77 |
|
78 lua_State *outputL; |
|
79 luaL_Buffer yamlbuf; |
|
80 }; |
|
81 |
|
82 static int l_null(lua_State *); |
|
83 |
|
84 static void generate_error_message(struct lua_yaml_loader *loader) { |
|
85 char buf[256]; |
|
86 luaL_Buffer b; |
|
87 |
|
88 luaL_buffinit(loader->L, &b); |
|
89 |
|
90 luaL_addstring(&b, loader->parser.problem ? loader->parser.problem : "A problem"); |
|
91 snprintf(buf, sizeof(buf), " at document: %d", loader->document_count); |
|
92 luaL_addstring(&b, buf); |
|
93 |
|
94 if (loader->parser.problem_mark.line || loader->parser.problem_mark.column) { |
|
95 snprintf(buf, sizeof(buf), ", line: %d, column: %d\n", |
|
96 loader->parser.problem_mark.line + 1, |
|
97 loader->parser.problem_mark.column + 1); |
|
98 luaL_addstring(&b, buf); |
|
99 } else { |
|
100 luaL_addstring(&b, "\n"); |
|
101 } |
|
102 |
|
103 if (loader->parser.context) { |
|
104 snprintf(buf, sizeof(buf), "%s at line: %d, column: %d\n", |
|
105 loader->parser.context, |
|
106 loader->parser.context_mark.line + 1, |
|
107 loader->parser.context_mark.column + 1); |
|
108 luaL_addstring(&b, buf); |
|
109 } |
|
110 |
|
111 luaL_pushresult(&b); |
|
112 } |
|
113 |
|
114 static inline void delete_event(struct lua_yaml_loader *loader) { |
|
115 if (loader->validevent) { |
|
116 yaml_event_delete(&loader->event); |
|
117 loader->validevent = 0; |
|
118 } |
|
119 } |
|
120 |
|
121 static inline int do_parse(struct lua_yaml_loader *loader) { |
|
122 delete_event(loader); |
|
123 if (yaml_parser_parse(&loader->parser, &loader->event) != 1) { |
|
124 generate_error_message(loader); |
|
125 loader->error = 1; |
|
126 return 0; |
|
127 } |
|
128 |
|
129 loader->validevent = 1; |
|
130 return 1; |
|
131 } |
|
132 |
|
133 static int load_node(struct lua_yaml_loader *loader); |
|
134 |
|
135 static void handle_anchor(struct lua_yaml_loader *loader) { |
|
136 const char *anchor = (char *)loader->event.data.scalar.anchor; |
|
137 if (!anchor) |
|
138 return; |
|
139 |
|
140 lua_pushstring(loader->L, anchor); |
|
141 lua_pushvalue(loader->L, -2); |
|
142 lua_rawset(loader->L, loader->anchortable_index); |
|
143 } |
|
144 |
|
145 static void load_map(struct lua_yaml_loader *loader) { |
|
146 lua_newtable(loader->L); |
|
147 if (loader->mapmt_index != 0) { |
|
148 lua_pushvalue(loader->L, loader->mapmt_index); |
|
149 lua_setmetatable(loader->L, -2); |
|
150 } |
|
151 |
|
152 handle_anchor(loader); |
|
153 while (1) { |
|
154 int r; |
|
155 /* load key */ |
|
156 if (load_node(loader) == 0 || loader->error) |
|
157 return; |
|
158 |
|
159 /* load value */ |
|
160 r = load_node(loader); |
|
161 if (loader->error) |
|
162 return; |
|
163 if (r != 1) |
|
164 RETURN_ERRMSG(loader, "unanticipated END event"); |
|
165 lua_rawset(loader->L, -3); |
|
166 } |
|
167 } |
|
168 |
|
169 static void load_sequence(struct lua_yaml_loader *loader) { |
|
170 int index = 1; |
|
171 |
|
172 lua_newtable(loader->L); |
|
173 if (loader->sequencemt_index != 0) { |
|
174 lua_pushvalue(loader->L, loader->sequencemt_index); |
|
175 lua_setmetatable(loader->L, -2); |
|
176 } |
|
177 |
|
178 handle_anchor(loader); |
|
179 while (load_node(loader) == 1 && !loader->error) |
|
180 lua_rawseti(loader->L, -2, index++); |
|
181 } |
|
182 |
|
183 static void load_scalar(struct lua_yaml_loader *loader) { |
|
184 const char *str = (char *)loader->event.data.scalar.value; |
|
185 unsigned int length = loader->event.data.scalar.length; |
|
186 const char *tag = (char *)loader->event.data.scalar.tag; |
|
187 |
|
188 if (tag && !strncmp(tag, LUAYAML_TAG_PREFIX, sizeof(LUAYAML_TAG_PREFIX) - 1)) { |
|
189 tag += sizeof(LUAYAML_TAG_PREFIX) - 1; |
|
190 |
|
191 if (!strcmp(tag, "str")) { |
|
192 lua_pushlstring(loader->L, str, length); |
|
193 return; |
|
194 } else if (!strcmp(tag, "int")) { |
|
195 lua_pushinteger(loader->L, strtol(str, NULL, 10)); |
|
196 return; |
|
197 } else if (!strcmp(tag, "float")) { |
|
198 lua_pushnumber(loader->L, strtod(str, NULL)); |
|
199 return; |
|
200 } else if (!strcmp(tag, "bool")) { |
|
201 lua_pushboolean(loader->L, !strcmp(str, "true") || !strcmp(str, "yes")); |
|
202 return; |
|
203 } else if (!strcmp(tag, "binary")) { |
|
204 frombase64(loader->L, (const unsigned char *)str, length); |
|
205 return; |
|
206 } |
|
207 } |
|
208 |
|
209 if (loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) { |
|
210 if (!strcmp(str, "~")) { |
|
211 if (Load_Nulls_As_Nil) |
|
212 lua_pushnil(loader->L); |
|
213 else |
|
214 l_null(loader->L); |
|
215 return; |
|
216 } else if (!strcmp(str, "true") || !strcmp(str, "yes")) { |
|
217 lua_pushboolean(loader->L, 1); |
|
218 return; |
|
219 } else if (!strcmp(str, "false") || !strcmp(str, "no")) { |
|
220 lua_pushboolean(loader->L, 0); |
|
221 return; |
|
222 } |
|
223 } |
|
224 |
|
225 lua_pushlstring(loader->L, str, length); |
|
226 |
|
227 /* plain scalar and Lua can convert it to a number? make it so... */ |
|
228 if (Load_Numeric_Scalars |
|
229 && loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE |
|
230 && lua_isnumber(loader->L, -1)) { |
|
231 lua_Number n = lua_tonumber(loader->L, -1); |
|
232 lua_pop(loader->L, 1); |
|
233 lua_pushnumber(loader->L, n); |
|
234 } |
|
235 |
|
236 handle_anchor(loader); |
|
237 } |
|
238 |
|
239 static void load_alias(struct lua_yaml_loader *loader) { |
|
240 char *anchor = (char *)loader->event.data.alias.anchor; |
|
241 lua_pushstring(loader->L, anchor); |
|
242 lua_rawget(loader->L, loader->anchortable_index); |
|
243 if (lua_isnil(loader->L, -1)) { |
|
244 char buf[256]; |
|
245 snprintf(buf, sizeof(buf), "invalid reference: %s", anchor); |
|
246 RETURN_ERRMSG(loader, buf); |
|
247 } |
|
248 } |
|
249 |
|
250 static int load_node(struct lua_yaml_loader *loader) { |
|
251 if (!do_parse(loader)) |
|
252 return -1; |
|
253 |
|
254 switch (loader->event.type) { |
|
255 case YAML_DOCUMENT_END_EVENT: |
|
256 case YAML_MAPPING_END_EVENT: |
|
257 case YAML_SEQUENCE_END_EVENT: |
|
258 return 0; |
|
259 |
|
260 case YAML_MAPPING_START_EVENT: |
|
261 load_map(loader); |
|
262 return 1; |
|
263 |
|
264 case YAML_SEQUENCE_START_EVENT: |
|
265 load_sequence(loader); |
|
266 return 1; |
|
267 |
|
268 case YAML_SCALAR_EVENT: |
|
269 load_scalar(loader); |
|
270 return 1; |
|
271 |
|
272 case YAML_ALIAS_EVENT: |
|
273 load_alias(loader); |
|
274 return 1; |
|
275 |
|
276 case YAML_NO_EVENT: |
|
277 lua_pushliteral(loader->L, "libyaml returned YAML_NO_EVENT"); |
|
278 loader->error = 1; |
|
279 return -1; |
|
280 |
|
281 default: |
|
282 lua_pushliteral(loader->L, "invalid event"); |
|
283 loader->error = 1; |
|
284 return -1; |
|
285 } |
|
286 } |
|
287 |
|
288 static void load(struct lua_yaml_loader *loader) { |
|
289 if (!do_parse(loader)) |
|
290 return; |
|
291 |
|
292 if (loader->event.type != YAML_STREAM_START_EVENT) |
|
293 RETURN_ERRMSG(loader, "expected STREAM_START_EVENT"); |
|
294 |
|
295 while (1) { |
|
296 if (!do_parse(loader)) |
|
297 return; |
|
298 |
|
299 if (loader->event.type == YAML_STREAM_END_EVENT) |
|
300 return; |
|
301 |
|
302 loader->document_count++; |
|
303 if (load_node(loader) != 1) |
|
304 RETURN_ERRMSG(loader, "unexpected END event"); |
|
305 if (loader->error) |
|
306 return; |
|
307 |
|
308 if (!do_parse(loader)) |
|
309 return; |
|
310 if (loader->event.type != YAML_DOCUMENT_END_EVENT) |
|
311 RETURN_ERRMSG(loader, "expected DOCUMENT_END_EVENT"); |
|
312 |
|
313 /* reset anchor table */ |
|
314 lua_newtable(loader->L); |
|
315 lua_replace(loader->L, loader->anchortable_index); |
|
316 } |
|
317 } |
|
318 |
|
319 static int l_load(lua_State *L) { |
|
320 struct lua_yaml_loader loader; |
|
321 int top = lua_gettop(L); |
|
322 |
|
323 luaL_argcheck(L, lua_isstring(L, 1), 1, "must provide a string argument"); |
|
324 |
|
325 loader.L = L; |
|
326 loader.validevent = 0; |
|
327 loader.error = 0; |
|
328 loader.document_count = 0; |
|
329 loader.mapmt_index = loader.sequencemt_index = 0; |
|
330 |
|
331 if (Load_Set_Metatables) { |
|
332 /* create sequence metatable */ |
|
333 lua_newtable(L); |
|
334 lua_pushliteral(L, "_yaml"); |
|
335 lua_pushliteral(L, "sequence"); |
|
336 lua_rawset(L, -3); |
|
337 loader.sequencemt_index = top + 1; |
|
338 |
|
339 /* create map metatable */ |
|
340 lua_newtable(L); |
|
341 lua_pushliteral(L, "_yaml"); |
|
342 lua_pushliteral(L, "map"); |
|
343 lua_rawset(L, -3); |
|
344 loader.mapmt_index = top + 2; |
|
345 } |
|
346 |
|
347 /* create table used to track anchors */ |
|
348 lua_newtable(L); |
|
349 loader.anchortable_index = lua_gettop(L); |
|
350 |
|
351 yaml_parser_initialize(&loader.parser); |
|
352 yaml_parser_set_input_string(&loader.parser, |
|
353 (const unsigned char *)lua_tostring(L, 1), lua_strlen(L, 1)); |
|
354 load(&loader); |
|
355 |
|
356 delete_event(&loader); |
|
357 yaml_parser_delete(&loader.parser); |
|
358 |
|
359 if (loader.error) |
|
360 lua_error(L); |
|
361 |
|
362 return loader.document_count; |
|
363 } |
|
364 |
|
365 static int dump_node(struct lua_yaml_dumper *dumper); |
|
366 |
|
367 static int is_binary_string(const unsigned char *str, size_t len) { |
|
368 // this could be optimized to examine an entire word in each loop iteration |
|
369 while (len-- > 0) { |
|
370 if (*str++ & 0x80) return 1; |
|
371 } |
|
372 return 0; |
|
373 } |
|
374 |
|
375 static int dump_scalar(struct lua_yaml_dumper *dumper) { |
|
376 int type = lua_type(dumper->L, -1); |
|
377 size_t len; |
|
378 const char *str = NULL; |
|
379 yaml_char_t *tag = NULL; |
|
380 yaml_event_t ev; |
|
381 yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE; |
|
382 int is_binary = 0; |
|
383 |
|
384 if (type == LUA_TSTRING) { |
|
385 str = lua_tolstring(dumper->L, -1, &len); |
|
386 if (len <= 5 && (!strcmp(str, "true") |
|
387 || !strcmp(str, "false") |
|
388 || !strcmp(str, "~"))) { |
|
389 style = YAML_SINGLE_QUOTED_SCALAR_STYLE; |
|
390 } else if (lua_isnumber(dumper->L, -1)) { |
|
391 /* string is convertible to number, quote it to preserve type */ |
|
392 style = YAML_SINGLE_QUOTED_SCALAR_STYLE; |
|
393 } else if ((is_binary = is_binary_string((const unsigned char *)str, len))) { |
|
394 tobase64(dumper->L, -1); |
|
395 str = lua_tolstring(dumper->L, -1, &len); |
|
396 tag = (yaml_char_t *) LUAYAML_TAG_PREFIX "binary"; |
|
397 } |
|
398 } else if (type == LUA_TNUMBER) { |
|
399 /* have Lua convert number to a string */ |
|
400 str = lua_tolstring(dumper->L, -1, &len); |
|
401 } else if (type == LUA_TBOOLEAN) { |
|
402 if (lua_toboolean(dumper->L, -1)) { |
|
403 str = "true"; |
|
404 len = 4; |
|
405 } else { |
|
406 str = "false"; |
|
407 len = 5; |
|
408 } |
|
409 } |
|
410 |
|
411 yaml_scalar_event_initialize( |
|
412 &ev, NULL, tag, (unsigned char *)str, len, |
|
413 !is_binary, !is_binary, style); |
|
414 if (is_binary) lua_pop(dumper->L, 1); |
|
415 return yaml_emitter_emit(&dumper->emitter, &ev); |
|
416 } |
|
417 |
|
418 static yaml_char_t *get_yaml_anchor(struct lua_yaml_dumper *dumper) { |
|
419 const char *s = ""; |
|
420 lua_pushvalue(dumper->L, -1); |
|
421 lua_rawget(dumper->L, dumper->anchortable_index); |
|
422 if (!lua_toboolean(dumper->L, -1)) { |
|
423 lua_pop(dumper->L, 1); |
|
424 return NULL; |
|
425 } |
|
426 |
|
427 if (lua_isboolean(dumper->L, -1)) { |
|
428 /* this element is referenced more than once but has not been named */ |
|
429 char buf[32]; |
|
430 snprintf(buf, sizeof(buf), "%u", dumper->anchor_number++); |
|
431 lua_pop(dumper->L, 1); |
|
432 lua_pushvalue(dumper->L, -1); |
|
433 lua_pushstring(dumper->L, buf); |
|
434 s = lua_tostring(dumper->L, -1); |
|
435 lua_rawset(dumper->L, dumper->anchortable_index); |
|
436 } else { |
|
437 /* this is an aliased element */ |
|
438 yaml_event_t ev; |
|
439 yaml_alias_event_initialize(&ev, (yaml_char_t *)lua_tostring(dumper->L, -1)); |
|
440 yaml_emitter_emit(&dumper->emitter, &ev); |
|
441 lua_pop(dumper->L, 1); |
|
442 } |
|
443 return (yaml_char_t *)s; |
|
444 } |
|
445 |
|
446 static int dump_table(struct lua_yaml_dumper *dumper) { |
|
447 yaml_event_t ev; |
|
448 yaml_char_t *anchor = get_yaml_anchor(dumper); |
|
449 |
|
450 if (anchor && !*anchor) return 1; |
|
451 |
|
452 yaml_mapping_start_event_initialize(&ev, anchor, NULL, 0, |
|
453 YAML_BLOCK_MAPPING_STYLE); |
|
454 yaml_emitter_emit(&dumper->emitter, &ev); |
|
455 |
|
456 lua_pushnil(dumper->L); |
|
457 while (lua_next(dumper->L, -2)) { |
|
458 lua_pushvalue(dumper->L, -2); /* push copy of key on top of stack */ |
|
459 if (!dump_node(dumper) || dumper->error) |
|
460 return 0; |
|
461 lua_pop(dumper->L, 1); /* pop copy of key */ |
|
462 if (!dump_node(dumper) || dumper->error) |
|
463 return 0; |
|
464 lua_pop(dumper->L, 1); |
|
465 } |
|
466 |
|
467 yaml_mapping_end_event_initialize(&ev); |
|
468 yaml_emitter_emit(&dumper->emitter, &ev); |
|
469 return 1; |
|
470 } |
|
471 |
|
472 static int dump_array(struct lua_yaml_dumper *dumper) { |
|
473 int i, n = luaL_getn(dumper->L, -1); |
|
474 yaml_event_t ev; |
|
475 yaml_char_t *anchor = get_yaml_anchor(dumper); |
|
476 |
|
477 if (anchor && !*anchor) |
|
478 return 1; |
|
479 |
|
480 yaml_sequence_start_event_initialize(&ev, anchor, NULL, 0, |
|
481 YAML_BLOCK_SEQUENCE_STYLE); |
|
482 yaml_emitter_emit(&dumper->emitter, &ev); |
|
483 |
|
484 for (i = 0; i < n; i++) { |
|
485 lua_rawgeti(dumper->L, -1, i + 1); |
|
486 if (!dump_node(dumper) || dumper->error) |
|
487 return 0; |
|
488 lua_pop(dumper->L, 1); |
|
489 } |
|
490 |
|
491 yaml_sequence_end_event_initialize(&ev); |
|
492 yaml_emitter_emit(&dumper->emitter, &ev); |
|
493 |
|
494 return 1; |
|
495 } |
|
496 |
|
497 static int figure_table_type(lua_State *L) { |
|
498 int type = LUAYAML_KIND_UNKNOWN; |
|
499 |
|
500 if (lua_getmetatable(L, -1)) { |
|
501 /* has metatable, look for _yaml key */ |
|
502 lua_pushliteral(L, "_yaml"); |
|
503 lua_rawget(L, -2); |
|
504 if (lua_isstring(L, -1)) { |
|
505 const char *s = lua_tostring(L, -1); |
|
506 if (!strcmp(s, "sequence") || !strcmp(s, "seq")) |
|
507 type = LUAYAML_KIND_SEQUENCE; |
|
508 else if (!strcmp(s, "map") || !strcmp(s, "mapping")) |
|
509 type = LUAYAML_KIND_MAPPING; |
|
510 } |
|
511 lua_pop(L, 2); /* pop value and metatable */ |
|
512 } |
|
513 |
|
514 return type; |
|
515 } |
|
516 |
|
517 static int dump_null(struct lua_yaml_dumper *dumper) { |
|
518 yaml_event_t ev; |
|
519 yaml_scalar_event_initialize(&ev, NULL, NULL, |
|
520 (unsigned char *)"~", 1, 1, 1, YAML_PLAIN_SCALAR_STYLE); |
|
521 return yaml_emitter_emit(&dumper->emitter, &ev); |
|
522 } |
|
523 |
|
524 static int dump_node(struct lua_yaml_dumper *dumper) { |
|
525 int type = lua_type(dumper->L, -1); |
|
526 |
|
527 if (type == LUA_TSTRING || type == LUA_TBOOLEAN || type == LUA_TNUMBER) { |
|
528 return dump_scalar(dumper); |
|
529 } else if (type == LUA_TTABLE) { |
|
530 int type = LUAYAML_KIND_UNKNOWN; |
|
531 |
|
532 if (Dump_Check_Metatables) |
|
533 type = figure_table_type(dumper->L); |
|
534 |
|
535 if (type == LUAYAML_KIND_UNKNOWN && Dump_Auto_Array && |
|
536 luaL_getn(dumper->L, -1) > 0) { |
|
537 type = LUAYAML_KIND_SEQUENCE; |
|
538 } |
|
539 |
|
540 if (type == LUAYAML_KIND_SEQUENCE) |
|
541 return dump_array(dumper); |
|
542 return dump_table(dumper); |
|
543 } else if (type == LUA_TFUNCTION && lua_tocfunction(dumper->L, -1) == l_null) { |
|
544 return dump_null(dumper); |
|
545 } else { /* unsupported Lua type */ |
|
546 if (Dump_Error_on_Unsupported) { |
|
547 char buf[256]; |
|
548 snprintf(buf, sizeof(buf), |
|
549 "cannot dump object of type: %s", lua_typename(dumper->L, type)); |
|
550 lua_pushstring(dumper->L, buf); |
|
551 dumper->error = 1; |
|
552 } else { |
|
553 return dump_null(dumper); |
|
554 } |
|
555 } |
|
556 return 0; |
|
557 } |
|
558 |
|
559 static void dump_document(struct lua_yaml_dumper *dumper) { |
|
560 yaml_event_t ev; |
|
561 |
|
562 yaml_document_start_event_initialize(&ev, NULL, NULL, NULL, 0); |
|
563 yaml_emitter_emit(&dumper->emitter, &ev); |
|
564 |
|
565 if (!dump_node(dumper) || dumper->error) |
|
566 return; |
|
567 |
|
568 yaml_document_end_event_initialize(&ev, 1); |
|
569 yaml_emitter_emit(&dumper->emitter, &ev); |
|
570 } |
|
571 |
|
572 static int append_output(void *arg, unsigned char *buf, unsigned int len) { |
|
573 struct lua_yaml_dumper *dumper = (struct lua_yaml_dumper *)arg; |
|
574 luaL_addlstring(&dumper->yamlbuf, (char *)buf, len); |
|
575 return 1; |
|
576 } |
|
577 |
|
578 static void find_references(struct lua_yaml_dumper *dumper) { |
|
579 int newval = -1, type = lua_type(dumper->L, -1); |
|
580 if (type != LUA_TTABLE) |
|
581 return; |
|
582 |
|
583 lua_pushvalue(dumper->L, -1); /* push copy of table */ |
|
584 lua_rawget(dumper->L, dumper->anchortable_index); |
|
585 if (lua_isnil(dumper->L, -1)) |
|
586 newval = 0; |
|
587 else if (!lua_toboolean(dumper->L, -1)) |
|
588 newval = 1; |
|
589 lua_pop(dumper->L, 1); |
|
590 if (newval != -1) { |
|
591 lua_pushvalue(dumper->L, -1); |
|
592 lua_pushboolean(dumper->L, newval); |
|
593 lua_rawset(dumper->L, dumper->anchortable_index); |
|
594 } |
|
595 if (newval) |
|
596 return; |
|
597 |
|
598 /* recursively process other table values */ |
|
599 lua_pushnil(dumper->L); |
|
600 while (lua_next(dumper->L, -2) != 0) { |
|
601 find_references(dumper); /* find references on value */ |
|
602 lua_pop(dumper->L, 1); |
|
603 find_references(dumper); /* find references on key */ |
|
604 } |
|
605 } |
|
606 |
|
607 static int l_dump(lua_State *L) { |
|
608 struct lua_yaml_dumper dumper; |
|
609 int i, argcount = lua_gettop(L); |
|
610 yaml_event_t ev; |
|
611 |
|
612 dumper.L = L; |
|
613 dumper.error = 0; |
|
614 /* create thread to use for YAML buffer */ |
|
615 dumper.outputL = lua_newthread(L); |
|
616 luaL_buffinit(dumper.outputL, &dumper.yamlbuf); |
|
617 |
|
618 yaml_emitter_initialize(&dumper.emitter); |
|
619 yaml_emitter_set_unicode(&dumper.emitter, 1); |
|
620 yaml_emitter_set_width(&dumper.emitter, 2); |
|
621 yaml_emitter_set_output(&dumper.emitter, &append_output, &dumper); |
|
622 |
|
623 yaml_stream_start_event_initialize(&ev, YAML_UTF8_ENCODING); |
|
624 yaml_emitter_emit(&dumper.emitter, &ev); |
|
625 |
|
626 for (i = 0; i < argcount; i++) { |
|
627 lua_newtable(L); |
|
628 dumper.anchortable_index = lua_gettop(L); |
|
629 dumper.anchor_number = 0; |
|
630 lua_pushvalue(L, i + 1); /* push copy of arg we're processing */ |
|
631 find_references(&dumper); |
|
632 dump_document(&dumper); |
|
633 if (dumper.error) |
|
634 break; |
|
635 lua_pop(L, 2); /* pop copied arg and anchor table */ |
|
636 } |
|
637 |
|
638 yaml_stream_end_event_initialize(&ev); |
|
639 yaml_emitter_emit(&dumper.emitter, &ev); |
|
640 |
|
641 yaml_emitter_flush(&dumper.emitter); |
|
642 yaml_emitter_delete(&dumper.emitter); |
|
643 |
|
644 /* finalize and push YAML buffer */ |
|
645 luaL_pushresult(&dumper.yamlbuf); |
|
646 |
|
647 if (dumper.error) |
|
648 lua_error(L); |
|
649 |
|
650 /* move buffer to original thread */ |
|
651 lua_xmove(dumper.outputL, L, 1); |
|
652 return 1; |
|
653 } |
|
654 |
|
655 static int handle_config_option(lua_State *L) { |
|
656 const char *attr; |
|
657 int i; |
|
658 static const struct { |
|
659 const char *attr; |
|
660 char *val; |
|
661 } args[] = { |
|
662 { "dump_auto_array", &Dump_Auto_Array }, |
|
663 { "dump_check_metatables", &Dump_Check_Metatables }, |
|
664 { "dump_error_on_unsupported", &Dump_Error_on_Unsupported }, |
|
665 { "load_set_metatables", &Load_Set_Metatables }, |
|
666 { "load_numeric_scalars", &Load_Numeric_Scalars }, |
|
667 { "load_nulls_as_nil", &Load_Nulls_As_Nil }, |
|
668 { NULL, NULL } |
|
669 }; |
|
670 |
|
671 luaL_argcheck(L, lua_isstring(L, -2), 1, "config attribute must be string"); |
|
672 luaL_argcheck(L, lua_isboolean(L, -1) || lua_isnil(L, -1), 1, |
|
673 "value must be boolean or nil"); |
|
674 |
|
675 attr = lua_tostring(L, -2); |
|
676 for (i = 0; args[i].attr; i++) { |
|
677 if (!strcmp(attr, args[i].attr)) { |
|
678 if (!lua_isnil(L, -1)) |
|
679 *(args[i].val) = lua_toboolean(L, -1); |
|
680 lua_pushboolean(L, *(args[i].val)); |
|
681 return 1; |
|
682 } |
|
683 } |
|
684 |
|
685 luaL_error(L, "unrecognized config option: %s", attr); |
|
686 return 0; /* never reached */ |
|
687 } |
|
688 |
|
689 static int l_config(lua_State *L) { |
|
690 if (lua_istable(L, -1)) { |
|
691 lua_pushnil(L); |
|
692 while (lua_next(L, -2) != 0) { |
|
693 handle_config_option(L); |
|
694 lua_pop(L, 2); |
|
695 } |
|
696 return 0; |
|
697 } |
|
698 |
|
699 return handle_config_option(L); |
|
700 } |
|
701 |
|
702 static int l_null(lua_State *L) { |
|
703 lua_getglobal(L, "yaml"); |
|
704 lua_pushliteral(L, "null"); |
|
705 lua_rawget(L, -2); |
|
706 lua_replace(L, -2); |
|
707 |
|
708 return 1; |
|
709 } |
|
710 |
|
711 LUALIB_API int luaopen_yaml(lua_State *L) { |
|
712 const luaL_reg yamllib[] = { |
|
713 { "load", l_load }, |
|
714 { "dump", l_dump }, |
|
715 { "configure", l_config }, |
|
716 { "null", l_null }, |
|
717 { NULL, NULL} |
|
718 }; |
|
719 |
|
720 luaL_openlib(L, "yaml", yamllib, 0); |
|
721 return 1; |
|
722 } |