Fri, 16 Dec 2011 23:21:28 +0000
Initial commit with my changes.
Originally imported from yaml-2.0.tar.gz: http://files.luaforge.net/releases/yaml/yaml/0.2/yaml-0.2.tar.gz
/* * lyaml.c, LibYAML binding for Lua * * Copyright (c) 2009, Andrew Danforth <acd@weirdness.net> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * Portions of this software were inspired by Perl's YAML::LibYAML module by * Ingy döt Net <ingy@cpan.org> * */ #include <string.h> #include <stdlib.h> #include <lauxlib.h> #include <lua.h> #include <lualib.h> #include "yaml.h" #include "b64.h" /* configurable flags */ static char Dump_Auto_Array = 1; static char Dump_Error_on_Unsupported = 0; static char Dump_Check_Metatables = 1; static char Load_Set_Metatables = 1; static char Load_Numeric_Scalars = 1; static char Load_Nulls_As_Nil = 0; #define LUAYAML_TAG_PREFIX "tag:yaml.org,2002:" #define LUAYAML_KIND_UNKNOWN 0 #define LUAYAML_KIND_SEQUENCE 1 #define LUAYAML_KIND_MAPPING 2 #define RETURN_ERRMSG(s, msg) do { \ lua_pushstring(s->L, msg); \ s->error = 1; \ return; \ } while(0) struct lua_yaml_loader { lua_State *L; int anchortable_index; int sequencemt_index; int mapmt_index; int document_count; yaml_parser_t parser; yaml_event_t event; char validevent; char error; }; struct lua_yaml_dumper { lua_State *L; int anchortable_index; unsigned int anchor_number; yaml_emitter_t emitter; char error; lua_State *outputL; luaL_Buffer yamlbuf; }; static int l_null(lua_State *); static void generate_error_message(struct lua_yaml_loader *loader) { char buf[256]; luaL_Buffer b; luaL_buffinit(loader->L, &b); luaL_addstring(&b, loader->parser.problem ? loader->parser.problem : "A problem"); snprintf(buf, sizeof(buf), " at document: %d", loader->document_count); luaL_addstring(&b, buf); if (loader->parser.problem_mark.line || loader->parser.problem_mark.column) { snprintf(buf, sizeof(buf), ", line: %d, column: %d\n", loader->parser.problem_mark.line + 1, loader->parser.problem_mark.column + 1); luaL_addstring(&b, buf); } else { luaL_addstring(&b, "\n"); } if (loader->parser.context) { snprintf(buf, sizeof(buf), "%s at line: %d, column: %d\n", loader->parser.context, loader->parser.context_mark.line + 1, loader->parser.context_mark.column + 1); luaL_addstring(&b, buf); } luaL_pushresult(&b); } static inline void delete_event(struct lua_yaml_loader *loader) { if (loader->validevent) { yaml_event_delete(&loader->event); loader->validevent = 0; } } static inline int do_parse(struct lua_yaml_loader *loader) { delete_event(loader); if (yaml_parser_parse(&loader->parser, &loader->event) != 1) { generate_error_message(loader); loader->error = 1; return 0; } loader->validevent = 1; return 1; } static int load_node(struct lua_yaml_loader *loader); static void handle_anchor(struct lua_yaml_loader *loader) { const char *anchor = (char *)loader->event.data.scalar.anchor; if (!anchor) return; lua_pushstring(loader->L, anchor); lua_pushvalue(loader->L, -2); lua_rawset(loader->L, loader->anchortable_index); } static void load_map(struct lua_yaml_loader *loader) { lua_newtable(loader->L); if (loader->mapmt_index != 0) { lua_pushvalue(loader->L, loader->mapmt_index); lua_setmetatable(loader->L, -2); } handle_anchor(loader); while (1) { int r; /* load key */ if (load_node(loader) == 0 || loader->error) return; /* load value */ r = load_node(loader); if (loader->error) return; if (r != 1) RETURN_ERRMSG(loader, "unanticipated END event"); lua_rawset(loader->L, -3); } } static void load_sequence(struct lua_yaml_loader *loader) { int index = 1; lua_newtable(loader->L); if (loader->sequencemt_index != 0) { lua_pushvalue(loader->L, loader->sequencemt_index); lua_setmetatable(loader->L, -2); } handle_anchor(loader); while (load_node(loader) == 1 && !loader->error) lua_rawseti(loader->L, -2, index++); } static void load_scalar(struct lua_yaml_loader *loader) { const char *str = (char *)loader->event.data.scalar.value; unsigned int length = loader->event.data.scalar.length; const char *tag = (char *)loader->event.data.scalar.tag; if (tag && !strncmp(tag, LUAYAML_TAG_PREFIX, sizeof(LUAYAML_TAG_PREFIX) - 1)) { tag += sizeof(LUAYAML_TAG_PREFIX) - 1; if (!strcmp(tag, "str")) { lua_pushlstring(loader->L, str, length); return; } else if (!strcmp(tag, "int")) { lua_pushinteger(loader->L, strtol(str, NULL, 10)); return; } else if (!strcmp(tag, "float")) { lua_pushnumber(loader->L, strtod(str, NULL)); return; } else if (!strcmp(tag, "bool")) { lua_pushboolean(loader->L, !strcmp(str, "true") || !strcmp(str, "yes")); return; } else if (!strcmp(tag, "binary")) { frombase64(loader->L, (const unsigned char *)str, length); return; } } if (loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) { if (!strcmp(str, "~")) { if (Load_Nulls_As_Nil) lua_pushnil(loader->L); else l_null(loader->L); return; } else if (!strcmp(str, "true") || !strcmp(str, "yes")) { lua_pushboolean(loader->L, 1); return; } else if (!strcmp(str, "false") || !strcmp(str, "no")) { lua_pushboolean(loader->L, 0); return; } } lua_pushlstring(loader->L, str, length); /* plain scalar and Lua can convert it to a number? make it so... */ if (Load_Numeric_Scalars && loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE && lua_isnumber(loader->L, -1)) { lua_Number n = lua_tonumber(loader->L, -1); lua_pop(loader->L, 1); lua_pushnumber(loader->L, n); } handle_anchor(loader); } static void load_alias(struct lua_yaml_loader *loader) { char *anchor = (char *)loader->event.data.alias.anchor; lua_pushstring(loader->L, anchor); lua_rawget(loader->L, loader->anchortable_index); if (lua_isnil(loader->L, -1)) { char buf[256]; snprintf(buf, sizeof(buf), "invalid reference: %s", anchor); RETURN_ERRMSG(loader, buf); } } static int load_node(struct lua_yaml_loader *loader) { if (!do_parse(loader)) return -1; switch (loader->event.type) { case YAML_DOCUMENT_END_EVENT: case YAML_MAPPING_END_EVENT: case YAML_SEQUENCE_END_EVENT: return 0; case YAML_MAPPING_START_EVENT: load_map(loader); return 1; case YAML_SEQUENCE_START_EVENT: load_sequence(loader); return 1; case YAML_SCALAR_EVENT: load_scalar(loader); return 1; case YAML_ALIAS_EVENT: load_alias(loader); return 1; case YAML_NO_EVENT: lua_pushliteral(loader->L, "libyaml returned YAML_NO_EVENT"); loader->error = 1; return -1; default: lua_pushliteral(loader->L, "invalid event"); loader->error = 1; return -1; } } static void load(struct lua_yaml_loader *loader) { if (!do_parse(loader)) return; if (loader->event.type != YAML_STREAM_START_EVENT) RETURN_ERRMSG(loader, "expected STREAM_START_EVENT"); while (1) { if (!do_parse(loader)) return; if (loader->event.type == YAML_STREAM_END_EVENT) return; loader->document_count++; if (load_node(loader) != 1) RETURN_ERRMSG(loader, "unexpected END event"); if (loader->error) return; if (!do_parse(loader)) return; if (loader->event.type != YAML_DOCUMENT_END_EVENT) RETURN_ERRMSG(loader, "expected DOCUMENT_END_EVENT"); /* reset anchor table */ lua_newtable(loader->L); lua_replace(loader->L, loader->anchortable_index); } } static int l_load(lua_State *L) { struct lua_yaml_loader loader; int top = lua_gettop(L); luaL_argcheck(L, lua_isstring(L, 1), 1, "must provide a string argument"); loader.L = L; loader.validevent = 0; loader.error = 0; loader.document_count = 0; loader.mapmt_index = loader.sequencemt_index = 0; if (Load_Set_Metatables) { /* create sequence metatable */ lua_newtable(L); lua_pushliteral(L, "_yaml"); lua_pushliteral(L, "sequence"); lua_rawset(L, -3); loader.sequencemt_index = top + 1; /* create map metatable */ lua_newtable(L); lua_pushliteral(L, "_yaml"); lua_pushliteral(L, "map"); lua_rawset(L, -3); loader.mapmt_index = top + 2; } /* create table used to track anchors */ lua_newtable(L); loader.anchortable_index = lua_gettop(L); yaml_parser_initialize(&loader.parser); yaml_parser_set_input_string(&loader.parser, (const unsigned char *)lua_tostring(L, 1), lua_strlen(L, 1)); load(&loader); delete_event(&loader); yaml_parser_delete(&loader.parser); if (loader.error) lua_error(L); return loader.document_count; } static int dump_node(struct lua_yaml_dumper *dumper); static int is_binary_string(const unsigned char *str, size_t len) { // this could be optimized to examine an entire word in each loop iteration while (len-- > 0) { if (*str++ & 0x80) return 1; } return 0; } static int dump_scalar(struct lua_yaml_dumper *dumper) { int type = lua_type(dumper->L, -1); size_t len; const char *str = NULL; yaml_char_t *tag = NULL; yaml_event_t ev; yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE; int is_binary = 0; if (type == LUA_TSTRING) { str = lua_tolstring(dumper->L, -1, &len); if (len <= 5 && (!strcmp(str, "true") || !strcmp(str, "false") || !strcmp(str, "~"))) { style = YAML_SINGLE_QUOTED_SCALAR_STYLE; } else if (lua_isnumber(dumper->L, -1)) { /* string is convertible to number, quote it to preserve type */ style = YAML_SINGLE_QUOTED_SCALAR_STYLE; } else if ((is_binary = is_binary_string((const unsigned char *)str, len))) { tobase64(dumper->L, -1); str = lua_tolstring(dumper->L, -1, &len); tag = (yaml_char_t *) LUAYAML_TAG_PREFIX "binary"; } } else if (type == LUA_TNUMBER) { /* have Lua convert number to a string */ str = lua_tolstring(dumper->L, -1, &len); } else if (type == LUA_TBOOLEAN) { if (lua_toboolean(dumper->L, -1)) { str = "true"; len = 4; } else { str = "false"; len = 5; } } yaml_scalar_event_initialize( &ev, NULL, tag, (unsigned char *)str, len, !is_binary, !is_binary, style); if (is_binary) lua_pop(dumper->L, 1); return yaml_emitter_emit(&dumper->emitter, &ev); } static yaml_char_t *get_yaml_anchor(struct lua_yaml_dumper *dumper) { const char *s = ""; lua_pushvalue(dumper->L, -1); lua_rawget(dumper->L, dumper->anchortable_index); if (!lua_toboolean(dumper->L, -1)) { lua_pop(dumper->L, 1); return NULL; } if (lua_isboolean(dumper->L, -1)) { /* this element is referenced more than once but has not been named */ char buf[32]; snprintf(buf, sizeof(buf), "%u", dumper->anchor_number++); lua_pop(dumper->L, 1); lua_pushvalue(dumper->L, -1); lua_pushstring(dumper->L, buf); s = lua_tostring(dumper->L, -1); lua_rawset(dumper->L, dumper->anchortable_index); } else { /* this is an aliased element */ yaml_event_t ev; yaml_alias_event_initialize(&ev, (yaml_char_t *)lua_tostring(dumper->L, -1)); yaml_emitter_emit(&dumper->emitter, &ev); lua_pop(dumper->L, 1); } return (yaml_char_t *)s; } static int dump_table(struct lua_yaml_dumper *dumper) { yaml_event_t ev; yaml_char_t *anchor = get_yaml_anchor(dumper); if (anchor && !*anchor) return 1; yaml_mapping_start_event_initialize(&ev, anchor, NULL, 0, YAML_BLOCK_MAPPING_STYLE); yaml_emitter_emit(&dumper->emitter, &ev); lua_pushnil(dumper->L); while (lua_next(dumper->L, -2)) { lua_pushvalue(dumper->L, -2); /* push copy of key on top of stack */ if (!dump_node(dumper) || dumper->error) return 0; lua_pop(dumper->L, 1); /* pop copy of key */ if (!dump_node(dumper) || dumper->error) return 0; lua_pop(dumper->L, 1); } yaml_mapping_end_event_initialize(&ev); yaml_emitter_emit(&dumper->emitter, &ev); return 1; } static int dump_array(struct lua_yaml_dumper *dumper) { int i, n = luaL_getn(dumper->L, -1); yaml_event_t ev; yaml_char_t *anchor = get_yaml_anchor(dumper); if (anchor && !*anchor) return 1; yaml_sequence_start_event_initialize(&ev, anchor, NULL, 0, YAML_BLOCK_SEQUENCE_STYLE); yaml_emitter_emit(&dumper->emitter, &ev); for (i = 0; i < n; i++) { lua_rawgeti(dumper->L, -1, i + 1); if (!dump_node(dumper) || dumper->error) return 0; lua_pop(dumper->L, 1); } yaml_sequence_end_event_initialize(&ev); yaml_emitter_emit(&dumper->emitter, &ev); return 1; } static int figure_table_type(lua_State *L) { int type = LUAYAML_KIND_UNKNOWN; if (lua_getmetatable(L, -1)) { /* has metatable, look for _yaml key */ lua_pushliteral(L, "_yaml"); lua_rawget(L, -2); if (lua_isstring(L, -1)) { const char *s = lua_tostring(L, -1); if (!strcmp(s, "sequence") || !strcmp(s, "seq")) type = LUAYAML_KIND_SEQUENCE; else if (!strcmp(s, "map") || !strcmp(s, "mapping")) type = LUAYAML_KIND_MAPPING; } lua_pop(L, 2); /* pop value and metatable */ } return type; } static int dump_null(struct lua_yaml_dumper *dumper) { yaml_event_t ev; yaml_scalar_event_initialize(&ev, NULL, NULL, (unsigned char *)"~", 1, 1, 1, YAML_PLAIN_SCALAR_STYLE); return yaml_emitter_emit(&dumper->emitter, &ev); } static int dump_node(struct lua_yaml_dumper *dumper) { int type = lua_type(dumper->L, -1); if (type == LUA_TSTRING || type == LUA_TBOOLEAN || type == LUA_TNUMBER) { return dump_scalar(dumper); } else if (type == LUA_TTABLE) { int type = LUAYAML_KIND_UNKNOWN; if (Dump_Check_Metatables) type = figure_table_type(dumper->L); if (type == LUAYAML_KIND_UNKNOWN && Dump_Auto_Array && luaL_getn(dumper->L, -1) > 0) { type = LUAYAML_KIND_SEQUENCE; } if (type == LUAYAML_KIND_SEQUENCE) return dump_array(dumper); return dump_table(dumper); } else if (type == LUA_TFUNCTION && lua_tocfunction(dumper->L, -1) == l_null) { return dump_null(dumper); } else { /* unsupported Lua type */ if (Dump_Error_on_Unsupported) { char buf[256]; snprintf(buf, sizeof(buf), "cannot dump object of type: %s", lua_typename(dumper->L, type)); lua_pushstring(dumper->L, buf); dumper->error = 1; } else { return dump_null(dumper); } } return 0; } static void dump_document(struct lua_yaml_dumper *dumper) { yaml_event_t ev; yaml_document_start_event_initialize(&ev, NULL, NULL, NULL, 0); yaml_emitter_emit(&dumper->emitter, &ev); if (!dump_node(dumper) || dumper->error) return; yaml_document_end_event_initialize(&ev, 1); yaml_emitter_emit(&dumper->emitter, &ev); } static int append_output(void *arg, unsigned char *buf, unsigned int len) { struct lua_yaml_dumper *dumper = (struct lua_yaml_dumper *)arg; luaL_addlstring(&dumper->yamlbuf, (char *)buf, len); return 1; } static void find_references(struct lua_yaml_dumper *dumper) { int newval = -1, type = lua_type(dumper->L, -1); if (type != LUA_TTABLE) return; lua_pushvalue(dumper->L, -1); /* push copy of table */ lua_rawget(dumper->L, dumper->anchortable_index); if (lua_isnil(dumper->L, -1)) newval = 0; else if (!lua_toboolean(dumper->L, -1)) newval = 1; lua_pop(dumper->L, 1); if (newval != -1) { lua_pushvalue(dumper->L, -1); lua_pushboolean(dumper->L, newval); lua_rawset(dumper->L, dumper->anchortable_index); } if (newval) return; /* recursively process other table values */ lua_pushnil(dumper->L); while (lua_next(dumper->L, -2) != 0) { find_references(dumper); /* find references on value */ lua_pop(dumper->L, 1); find_references(dumper); /* find references on key */ } } static int l_dump(lua_State *L) { struct lua_yaml_dumper dumper; int i, argcount = lua_gettop(L); yaml_event_t ev; dumper.L = L; dumper.error = 0; /* create thread to use for YAML buffer */ dumper.outputL = lua_newthread(L); luaL_buffinit(dumper.outputL, &dumper.yamlbuf); yaml_emitter_initialize(&dumper.emitter); yaml_emitter_set_unicode(&dumper.emitter, 1); yaml_emitter_set_width(&dumper.emitter, 2); yaml_emitter_set_output(&dumper.emitter, &append_output, &dumper); yaml_stream_start_event_initialize(&ev, YAML_UTF8_ENCODING); yaml_emitter_emit(&dumper.emitter, &ev); for (i = 0; i < argcount; i++) { lua_newtable(L); dumper.anchortable_index = lua_gettop(L); dumper.anchor_number = 0; lua_pushvalue(L, i + 1); /* push copy of arg we're processing */ find_references(&dumper); dump_document(&dumper); if (dumper.error) break; lua_pop(L, 2); /* pop copied arg and anchor table */ } yaml_stream_end_event_initialize(&ev); yaml_emitter_emit(&dumper.emitter, &ev); yaml_emitter_flush(&dumper.emitter); yaml_emitter_delete(&dumper.emitter); /* finalize and push YAML buffer */ luaL_pushresult(&dumper.yamlbuf); if (dumper.error) lua_error(L); /* move buffer to original thread */ lua_xmove(dumper.outputL, L, 1); return 1; } static int handle_config_option(lua_State *L) { const char *attr; int i; static const struct { const char *attr; char *val; } args[] = { { "dump_auto_array", &Dump_Auto_Array }, { "dump_check_metatables", &Dump_Check_Metatables }, { "dump_error_on_unsupported", &Dump_Error_on_Unsupported }, { "load_set_metatables", &Load_Set_Metatables }, { "load_numeric_scalars", &Load_Numeric_Scalars }, { "load_nulls_as_nil", &Load_Nulls_As_Nil }, { NULL, NULL } }; luaL_argcheck(L, lua_isstring(L, -2), 1, "config attribute must be string"); luaL_argcheck(L, lua_isboolean(L, -1) || lua_isnil(L, -1), 1, "value must be boolean or nil"); attr = lua_tostring(L, -2); for (i = 0; args[i].attr; i++) { if (!strcmp(attr, args[i].attr)) { if (!lua_isnil(L, -1)) *(args[i].val) = lua_toboolean(L, -1); lua_pushboolean(L, *(args[i].val)); return 1; } } luaL_error(L, "unrecognized config option: %s", attr); return 0; /* never reached */ } static int l_config(lua_State *L) { if (lua_istable(L, -1)) { lua_pushnil(L); while (lua_next(L, -2) != 0) { handle_config_option(L); lua_pop(L, 2); } return 0; } return handle_config_option(L); } static int l_null(lua_State *L) { lua_getglobal(L, "yaml"); lua_pushliteral(L, "null"); lua_rawget(L, -2); lua_replace(L, -2); return 1; } LUALIB_API int luaopen_yaml(lua_State *L) { const luaL_reg yamllib[] = { { "load", l_load }, { "dump", l_dump }, { "configure", l_config }, { "null", l_null }, { NULL, NULL} }; luaL_openlib(L, "yaml", yamllib, 0); return 1; }