├── .gitmodules ├── Makefile ├── README.md ├── yoml.h ├── test-yoml.c └── yoml-parser.h /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/picotest"] 2 | path = deps/picotest 3 | url = https://github.com/h2o/picotest.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | $(CC) -g -Wall $(COPTS) -I deps/picotest deps/picotest/picotest.c test-yoml.c -lyaml -o test-yoml 3 | ./test-yoml 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | YOML - a DOM-like interface to YAML 2 | ==== 3 | 4 | YOML is a DOM-like interface to YAML, implemented as a wrapper around [libyaml](http://pyyaml.org/wiki/LibYAML). 5 | 6 | It is a header-only library. Just include the .h files to use the library. 7 | 8 | ```C 9 | #include "yoml.h" /* defines the structures */ 10 | #include "yoml-parser.h" /* defines the parser */ 11 | 12 | static yoml_t *parse_file(FILE *fp) 13 | { 14 | yaml_parser_t parser; 15 | yoml_t *doc; 16 | 17 | yaml_parser_initialize(&parser); 18 | yaml_parser_set_input_file(&parser, fp); 19 | 20 | doc = yoml_parse_document(&parser, NULL); 21 | 22 | yaml_parser_delete(&parser); 23 | 24 | return doc; 25 | } 26 | 27 | static void dump_node(yoml_t *node, int indent) 28 | { 29 | size_t i; 30 | 31 | switch (node->type) { 32 | case YOML_TYPE_SCALAR: 33 | printf("%*s[SCALAR] %s\n", indent, "", node->data.scalar); 34 | break; 35 | case YOML_TYPE_SEQUENCE: 36 | printf("%*s[SEQUENCE] (size:%zu)\n", indent, "", node->data.sequence.size); 37 | for (i = 0; i != node->data.sequence.size; ++i) 38 | dump_node(node->data.sequence.elements[i], indent + 2); 39 | break; 40 | case YOML_TYPE_MAPPING: 41 | printf("%*s[MAPPING] (size:%zu)\n", indent, "", node->data.mapping.size); 42 | indent += 2; 43 | for (i = 0; i != node->data.mapping.size; ++i) { 44 | printf("%*s[KEY]\n", indent, ""); 45 | dump_node(node->data.mapping.elements[i].key, indent + 2); 46 | printf("%*s[VALUE]\n", indent, ""); 47 | dump_node(node->data.mapping.elements[i].value, indent + 2); 48 | } 49 | indent -= 2; 50 | break; 51 | } 52 | } 53 | 54 | static void dump_file(FILE *fp) 55 | { 56 | yoml_t *doc = parse_file(fp); 57 | 58 | if (doc == NULL) { 59 | fprintf(stderr, "parse error!\n"); /* error info can be obtained from yaml_parser_t */ 60 | return; 61 | } 62 | 63 | dump_node(doc, 0); 64 | yoml_free(doc); 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /yoml.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 DeNA Co., Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | #ifndef yoml_h 23 | #define yoml_h 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | typedef enum enum_yoml_type_t { YOML_TYPE_SCALAR, YOML_TYPE_SEQUENCE, YOML_TYPE_MAPPING, YOML__TYPE_UNRESOLVED_ALIAS } yoml_type_t; 34 | 35 | typedef struct st_yoml_t yoml_t; 36 | 37 | typedef struct st_yoml_sequence_t { 38 | size_t size; 39 | yoml_t *elements[]; 40 | } yoml_sequence_t; 41 | 42 | typedef struct st_yoml_mapping_element_t { 43 | yoml_t *key; 44 | yoml_t *value; 45 | } yoml_mapping_element_t; 46 | 47 | typedef struct st_yoml_mapping_t { 48 | size_t size; 49 | yoml_mapping_element_t elements[]; 50 | } yoml_mapping_t; 51 | 52 | struct st_yoml_t { 53 | yoml_type_t type; 54 | char *filename; 55 | size_t line; 56 | size_t column; 57 | char *anchor; 58 | char *tag; 59 | size_t _refcnt; 60 | unsigned _merged : 1; 61 | union { 62 | char *scalar; 63 | yoml_sequence_t sequence; 64 | yoml_mapping_t mapping; 65 | char *alias; 66 | } data; 67 | }; 68 | 69 | static inline void yoml_free(yoml_t *node, void *(*mem_set)(void *, int, size_t)) 70 | { 71 | size_t i; 72 | 73 | if (node == NULL) 74 | return; 75 | 76 | assert(node->_refcnt > 0); 77 | if (--node->_refcnt == 0) { 78 | free(node->filename); 79 | free(node->anchor); 80 | free(node->tag); 81 | switch (node->type) { 82 | case YOML_TYPE_SCALAR: 83 | if (mem_set != NULL) 84 | mem_set(node->data.scalar, 0, strlen(node->data.scalar)); 85 | free(node->data.scalar); 86 | break; 87 | case YOML_TYPE_SEQUENCE: 88 | for (i = 0; i != node->data.sequence.size; ++i) { 89 | yoml_free(node->data.sequence.elements[i], mem_set); 90 | } 91 | break; 92 | case YOML_TYPE_MAPPING: 93 | for (i = 0; i != node->data.mapping.size; ++i) { 94 | yoml_free(node->data.mapping.elements[i].key, mem_set); 95 | yoml_free(node->data.mapping.elements[i].value, mem_set); 96 | } 97 | break; 98 | case YOML__TYPE_UNRESOLVED_ALIAS: 99 | free(node->data.alias); 100 | break; 101 | } 102 | free(node); 103 | } 104 | } 105 | 106 | static inline yoml_t *yoml_find_anchor(yoml_t *node, const char *name) 107 | { 108 | yoml_t *n; 109 | size_t i; 110 | 111 | if (node->anchor != NULL && strcmp(node->anchor, name) == 0) 112 | return node; 113 | 114 | switch (node->type) { 115 | case YOML_TYPE_SEQUENCE: 116 | for (i = 0; i != node->data.sequence.size; ++i) 117 | if ((n = yoml_find_anchor(node->data.sequence.elements[i], name)) != NULL) 118 | return n; 119 | break; 120 | case YOML_TYPE_MAPPING: 121 | for (i = 0; i != node->data.mapping.size; ++i) 122 | if ((n = yoml_find_anchor(node->data.mapping.elements[i].key, name)) != NULL || 123 | (n = yoml_find_anchor(node->data.mapping.elements[i].value, name)) != NULL) 124 | return n; 125 | break; 126 | default: 127 | break; 128 | } 129 | 130 | return NULL; 131 | } 132 | 133 | static inline yoml_t *yoml_get(yoml_t *node, const char *name) 134 | { 135 | size_t i; 136 | 137 | if (node->type != YOML_TYPE_MAPPING) 138 | return NULL; 139 | for (i = 0; i != node->data.mapping.size; ++i) { 140 | yoml_t *key = node->data.mapping.elements[i].key; 141 | if (key->type == YOML_TYPE_SCALAR && strcmp(key->data.scalar, name) == 0) 142 | return node->data.mapping.elements[i].value; 143 | } 144 | return NULL; 145 | } 146 | 147 | #ifdef __cplusplus 148 | } 149 | #endif 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /test-yoml.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 DeNA Co., Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | #include 23 | #include "yoml.h" 24 | #include "yoml-parser.h" 25 | 26 | #include "picotest.h" 27 | 28 | static yoml_t *parse(const char *fn, const char *s) 29 | { 30 | yaml_parser_t parser; 31 | yoml_t *doc; 32 | 33 | yaml_parser_initialize(&parser); 34 | yaml_parser_set_input_string(&parser, (yaml_char_t*)s, strlen(s)); 35 | yoml_parse_args_t args = (yoml_parse_args_t){ .filename = fn, .resolve_alias = 1, .resolve_merge = 1 }; 36 | doc = yoml_parse_document(&parser, NULL, &args); 37 | yaml_parser_delete(&parser); 38 | 39 | return doc; 40 | } 41 | 42 | static yoml_t *get_value(yoml_t *mapping, const char *key) 43 | { 44 | size_t i; 45 | for (i = 0; i != mapping->data.mapping.size; ++i) 46 | if (mapping->data.mapping.elements[i].key->type == YOML_TYPE_SCALAR && 47 | strcmp(mapping->data.mapping.elements[i].key->data.scalar, key) == 0) 48 | return mapping->data.mapping.elements[i].value; 49 | return NULL; 50 | } 51 | 52 | int main(int argc, char **argv) 53 | { 54 | yoml_t *doc, *t; 55 | size_t i; 56 | 57 | doc = parse("foo.yaml", "abc"); 58 | ok(doc != NULL); 59 | ok(strcmp(doc->filename, "foo.yaml") == 0); 60 | ok(doc->type == YOML_TYPE_SCALAR); 61 | ok(strcmp(doc->data.scalar, "abc") == 0); 62 | yoml_free(doc, NULL); 63 | 64 | doc = parse( 65 | "foo.yaml", 66 | "---\n" 67 | "a: b\n" 68 | "c: d\n" 69 | "---\n" 70 | "e: f\n"); 71 | ok(doc != NULL); 72 | ok(strcmp(doc->filename, "foo.yaml") == 0); 73 | ok(doc->type == YOML_TYPE_MAPPING); 74 | ok(doc->data.mapping.size == 2); 75 | t = doc->data.mapping.elements[0].key; 76 | ok(strcmp(t->filename, "foo.yaml") == 0); 77 | ok(t->type == YOML_TYPE_SCALAR); 78 | ok(strcmp(t->data.scalar, "a") == 0); 79 | t = doc->data.mapping.elements[0].value; 80 | ok(strcmp(t->filename, "foo.yaml") == 0); 81 | ok(t->type == YOML_TYPE_SCALAR); 82 | ok(strcmp(t->data.scalar, "b") == 0); 83 | t = doc->data.mapping.elements[1].key; 84 | ok(strcmp(t->filename, "foo.yaml") == 0); 85 | ok(t->type == YOML_TYPE_SCALAR); 86 | ok(strcmp(t->data.scalar, "c") == 0); 87 | t = doc->data.mapping.elements[1].value; 88 | ok(strcmp(t->filename, "foo.yaml") == 0); 89 | ok(t->type == YOML_TYPE_SCALAR); 90 | ok(strcmp(t->data.scalar, "d") == 0); 91 | yoml_free(doc, NULL); 92 | 93 | doc = parse( 94 | "bar.yaml", 95 | "- a: b\n" 96 | " c: d\n" 97 | "- e\n"); 98 | ok(doc != NULL); 99 | ok(strcmp(doc->filename, "bar.yaml") == 0); 100 | ok(doc->type == YOML_TYPE_SEQUENCE); 101 | ok(doc->data.sequence.size == 2); 102 | t = doc->data.sequence.elements[0]; 103 | ok(strcmp(doc->filename, "bar.yaml") == 0); 104 | ok(t->type == YOML_TYPE_MAPPING); 105 | ok(t->data.mapping.size == 2); 106 | t = doc->data.sequence.elements[0]->data.mapping.elements[0].key; 107 | ok(strcmp(doc->filename, "bar.yaml") == 0); 108 | ok(t->type == YOML_TYPE_SCALAR); 109 | ok(strcmp(t->data.scalar, "a") == 0); 110 | t = doc->data.sequence.elements[0]->data.mapping.elements[0].value; 111 | ok(strcmp(doc->filename, "bar.yaml") == 0); 112 | ok(t->type == YOML_TYPE_SCALAR); 113 | ok(strcmp(t->data.scalar, "b") == 0); 114 | t = doc->data.sequence.elements[0]->data.mapping.elements[1].key; 115 | ok(strcmp(doc->filename, "bar.yaml") == 0); 116 | ok(t->type == YOML_TYPE_SCALAR); 117 | ok(strcmp(t->data.scalar, "c") == 0); 118 | t = doc->data.sequence.elements[0]->data.mapping.elements[1].value; 119 | ok(strcmp(doc->filename, "bar.yaml") == 0); 120 | ok(t->type == YOML_TYPE_SCALAR); 121 | ok(strcmp(t->data.scalar, "d") == 0); 122 | t = doc->data.sequence.elements[1]; 123 | ok(strcmp(doc->filename, "bar.yaml") == 0); 124 | ok(t->type == YOML_TYPE_SCALAR); 125 | ok(strcmp(t->data.scalar, "e") == 0); 126 | yoml_free(doc, NULL); 127 | 128 | doc = parse( 129 | "baz.yaml", 130 | "- &abc\n" 131 | " - 1\n" 132 | " - 2\n" 133 | "- *abc\n"); 134 | ok(doc != NULL); 135 | ok(strcmp(doc->filename, "baz.yaml") == 0); 136 | ok(doc->type == YOML_TYPE_SEQUENCE); 137 | ok(doc->data.sequence.size == 2); 138 | ok(doc->data.sequence.elements[0] == doc->data.sequence.elements[1]); 139 | t = doc->data.sequence.elements[0]; 140 | ok(strcmp(t->filename, "baz.yaml") == 0); 141 | ok(t->_refcnt == 2); 142 | ok(t->type == YOML_TYPE_SEQUENCE); 143 | ok(t->data.sequence.size == 2); 144 | t = doc->data.sequence.elements[0]->data.sequence.elements[0]; 145 | ok(strcmp(t->filename, "baz.yaml") == 0); 146 | ok(t->type == YOML_TYPE_SCALAR); 147 | ok(strcmp(t->data.scalar, "1") == 0); 148 | t = doc->data.sequence.elements[0]->data.sequence.elements[1]; 149 | ok(strcmp(t->filename, "baz.yaml") == 0); 150 | ok(t->type == YOML_TYPE_SCALAR); 151 | ok(strcmp(t->data.scalar, "2") == 0); 152 | 153 | doc = parse( 154 | "foo.yaml", 155 | "- &link\n" 156 | " x: 1\n" 157 | " y: 2\n" 158 | "- <<: *link\n" 159 | " y: 3\n"); 160 | ok(doc != NULL); 161 | ok(doc->type == YOML_TYPE_SEQUENCE); 162 | ok(doc->data.sequence.size == 2); 163 | ok(doc->data.sequence.elements[0]->type == YOML_TYPE_MAPPING); 164 | ok(doc->data.sequence.elements[0]->data.mapping.size == 2); 165 | ok(doc->data.sequence.elements[0]->data.mapping.elements[0].key->type == YOML_TYPE_SCALAR); 166 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[0].key->data.scalar, "x") == 0); 167 | ok(doc->data.sequence.elements[0]->data.mapping.elements[0].value->type == YOML_TYPE_SCALAR); 168 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[0].value->data.scalar, "1") == 0); 169 | ok(doc->data.sequence.elements[0]->data.mapping.elements[1].key->type == YOML_TYPE_SCALAR); 170 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[1].key->data.scalar, "y") == 0); 171 | ok(doc->data.sequence.elements[0]->data.mapping.elements[1].value->type == YOML_TYPE_SCALAR); 172 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[1].value->data.scalar, "2") == 0); 173 | ok(doc->data.sequence.elements[1]->data.mapping.elements[0].key->type == YOML_TYPE_SCALAR); 174 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[0].key->data.scalar, "x") == 0); 175 | ok(doc->data.sequence.elements[1]->data.mapping.elements[0].value->type == YOML_TYPE_SCALAR); 176 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[0].value->data.scalar, "1") == 0); 177 | ok(doc->data.sequence.elements[1]->data.mapping.elements[1].key->type == YOML_TYPE_SCALAR); 178 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[1].key->data.scalar, "y") == 0); 179 | ok(doc->data.sequence.elements[1]->data.mapping.elements[1].value->type == YOML_TYPE_SCALAR); 180 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[1].value->data.scalar, "3") == 0); 181 | 182 | doc = parse( 183 | "foo.yaml", 184 | "- &CENTER { x: 1, y: 2 }\n" 185 | "- &LEFT { x: 0, y: 2 }\n" 186 | "- &BIG { r: 10 }\n" 187 | "- &SMALL { r: 1 }\n" 188 | "- # Explicit keys\n" 189 | " x: 1\n" 190 | " y: 2\n" 191 | " r: 10\n" 192 | "- # Merge one map\n" 193 | " << : *CENTER\n" 194 | " r: 10\n" 195 | "- # Merge multiple maps\n" 196 | " << : [ *CENTER, *BIG ]\n" 197 | "- # Override\n" 198 | " << : [ *BIG, *LEFT, *SMALL ]\n" 199 | " x: 1\n"); 200 | ok(doc != NULL); 201 | ok(doc->type == YOML_TYPE_SEQUENCE); 202 | for (i = 4; i <= 7; ++i) { 203 | ok(doc->data.sequence.elements[i]->type == YOML_TYPE_MAPPING); 204 | ok(doc->data.sequence.elements[i]->data.mapping.size == 3); 205 | t = get_value(doc->data.sequence.elements[i], "x"); 206 | ok(t != NULL); 207 | ok(t->type == YOML_TYPE_SCALAR); 208 | ok(strcmp(t->data.scalar, "1") == 0); 209 | t = get_value(doc->data.sequence.elements[i], "y"); 210 | ok(t != NULL); 211 | ok(t->type == YOML_TYPE_SCALAR); 212 | ok(strcmp(t->data.scalar, "2") == 0); 213 | t = get_value(doc->data.sequence.elements[i], "r"); 214 | ok(t != NULL); 215 | ok(t->type == YOML_TYPE_SCALAR); 216 | ok(strcmp(t->data.scalar, "10") == 0); 217 | } 218 | 219 | doc = parse( 220 | "foo.yaml", 221 | "- &link\n" 222 | " x: 1\n" 223 | " x: 2\n" 224 | "-\n" 225 | " x: 3\n" 226 | " <<: *link\n"); 227 | ok(doc != NULL); 228 | ok(doc->type == YOML_TYPE_SEQUENCE); 229 | ok(doc->data.sequence.size == 2); 230 | ok(doc->data.sequence.elements[0]->type == YOML_TYPE_MAPPING); 231 | ok(doc->data.sequence.elements[0]->data.mapping.size == 2); 232 | ok(doc->data.sequence.elements[0]->data.mapping.elements[0].key->type == YOML_TYPE_SCALAR); 233 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[0].key->data.scalar, "x") == 0); 234 | ok(doc->data.sequence.elements[0]->data.mapping.elements[0].value->type == YOML_TYPE_SCALAR); 235 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[0].value->data.scalar, "1") == 0); 236 | ok(doc->data.sequence.elements[0]->data.mapping.elements[1].key->type == YOML_TYPE_SCALAR); 237 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[1].key->data.scalar, "x") == 0); 238 | ok(doc->data.sequence.elements[0]->data.mapping.elements[1].value->type == YOML_TYPE_SCALAR); 239 | ok(strcmp(doc->data.sequence.elements[0]->data.mapping.elements[1].value->data.scalar, "2") == 0); 240 | ok(doc->data.sequence.elements[1]->data.mapping.size == 3); 241 | ok(doc->data.sequence.elements[1]->data.mapping.elements[0].key->type == YOML_TYPE_SCALAR); 242 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[0].key->data.scalar, "x") == 0); 243 | ok(doc->data.sequence.elements[1]->data.mapping.elements[0].value->type == YOML_TYPE_SCALAR); 244 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[0].value->data.scalar, "3") == 0); 245 | ok(doc->data.sequence.elements[1]->data.mapping.elements[1].key->type == YOML_TYPE_SCALAR); 246 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[1].key->data.scalar, "x") == 0); 247 | ok(doc->data.sequence.elements[1]->data.mapping.elements[1].value->type == YOML_TYPE_SCALAR); 248 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[1].value->data.scalar, "1") == 0); 249 | ok(doc->data.sequence.elements[1]->data.mapping.elements[2].key->type == YOML_TYPE_SCALAR); 250 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[2].key->data.scalar, "x") == 0); 251 | ok(doc->data.sequence.elements[1]->data.mapping.elements[2].value->type == YOML_TYPE_SCALAR); 252 | ok(strcmp(doc->data.sequence.elements[1]->data.mapping.elements[2].value->data.scalar, "2") == 0); 253 | 254 | doc = parse( 255 | "foo.yaml", 256 | "a: &link1\n" 257 | " x: 1\n" 258 | "b: &link2\n" 259 | " <<: *link1\n" 260 | " y: 2\n" 261 | "c:\n" 262 | " <<: *link2\n" 263 | ); 264 | ok(doc != NULL); 265 | ok(doc->type == YOML_TYPE_MAPPING); 266 | ok(doc->data.mapping.size == 3); 267 | ok(doc->data.mapping.elements[2].value->type == YOML_TYPE_MAPPING); 268 | ok(doc->data.mapping.elements[2].value->data.mapping.size == 2); 269 | ok(doc->data.mapping.elements[2].value->data.mapping.elements[0].key->type == YOML_TYPE_SCALAR); 270 | ok(strcmp(doc->data.mapping.elements[2].value->data.mapping.elements[0].key->data.scalar, "x") == 0); 271 | ok(doc->data.mapping.elements[2].value->data.mapping.elements[0].value->type == YOML_TYPE_SCALAR); 272 | ok(strcmp(doc->data.mapping.elements[2].value->data.mapping.elements[0].value->data.scalar, "1") == 0); 273 | ok(doc->data.mapping.elements[2].value->data.mapping.elements[1].key->type == YOML_TYPE_SCALAR); 274 | ok(strcmp(doc->data.mapping.elements[2].value->data.mapping.elements[1].key->data.scalar, "y") == 0); 275 | ok(doc->data.mapping.elements[2].value->data.mapping.elements[1].value->type == YOML_TYPE_SCALAR); 276 | ok(strcmp(doc->data.mapping.elements[2].value->data.mapping.elements[1].value->data.scalar, "2") == 0); 277 | 278 | doc = parse( 279 | "foo.yaml", 280 | "a: &foo\n" 281 | " b: c\n" 282 | " <<:\n" 283 | " d: e\n" 284 | "f: *foo\n" 285 | ); 286 | ok(doc != NULL); 287 | ok(doc->type == YOML_TYPE_MAPPING); 288 | ok(doc->data.mapping.size == 2); 289 | ok(doc->data.mapping.elements[1].value->type == YOML_TYPE_MAPPING); 290 | ok(doc->data.mapping.elements[1].value->data.mapping.elements[0].key->type == YOML_TYPE_SCALAR); 291 | ok(strcmp(doc->data.mapping.elements[1].value->data.mapping.elements[0].key->data.scalar, "b") == 0); 292 | ok(doc->data.mapping.elements[1].value->data.mapping.elements[0].value->type == YOML_TYPE_SCALAR); 293 | ok(strcmp(doc->data.mapping.elements[1].value->data.mapping.elements[0].value->data.scalar, "c") == 0); 294 | ok(doc->data.mapping.elements[1].value->data.mapping.elements[1].key->type == YOML_TYPE_SCALAR); 295 | ok(strcmp(doc->data.mapping.elements[1].value->data.mapping.elements[1].key->data.scalar, "d") == 0); 296 | ok(doc->data.mapping.elements[1].value->data.mapping.elements[1].value->type == YOML_TYPE_SCALAR); 297 | ok(strcmp(doc->data.mapping.elements[1].value->data.mapping.elements[1].value->data.scalar, "e") == 0); 298 | 299 | /* in current implementation, merge generates a new node for each occurences of an alias, 300 | so these two nodes become different */ 301 | ok(doc->data.mapping.elements[0].value != doc->data.mapping.elements[1].value); 302 | ok(doc->data.mapping.elements[0].value->_refcnt == 1); 303 | ok(doc->data.mapping.elements[1].value->_refcnt == 1); 304 | 305 | 306 | return done_testing(); 307 | } 308 | -------------------------------------------------------------------------------- /yoml-parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 DeNA Co., Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | #ifndef yoml_parser_h 23 | #define yoml_parser_h 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "yoml.h" 35 | 36 | typedef struct st_yoml_parse_args_t { 37 | const char *filename; 38 | void *(*mem_set)(void *, int, size_t); 39 | struct { 40 | yoml_t *(*cb)(const char *tag, yoml_t *node, void *cb_arg); 41 | void *cb_arg; 42 | } resolve_tag; 43 | unsigned resolve_alias : 1; 44 | unsigned resolve_merge : 1; 45 | } yoml_parse_args_t; 46 | 47 | static yoml_t *yoml__parse_node(yaml_parser_t *parser, yaml_event_type_t *last_event, yoml_parse_args_t *parse_args); 48 | 49 | static inline char *yoml__strdup(yaml_char_t *s) 50 | { 51 | return strdup((char *)s); 52 | } 53 | 54 | static inline size_t yoml__scalar_get_line(yaml_event_t *event) 55 | { 56 | switch (event->data.scalar.style) { 57 | case YAML_LITERAL_SCALAR_STYLE: 58 | case YAML_FOLDED_SCALAR_STYLE: 59 | return event->start_mark.line + 1; 60 | default: 61 | return event->start_mark.line; 62 | } 63 | } 64 | 65 | static inline yoml_t *yoml__new_node(const char *filename, yoml_type_t type, size_t sz, yaml_char_t *anchor, yaml_char_t *tag, 66 | size_t line, size_t column) 67 | { 68 | yoml_t *node = malloc(sz); 69 | node->filename = filename != NULL ? strdup(filename) : NULL; 70 | node->type = type; 71 | node->line = line; 72 | node->column = column; 73 | node->anchor = anchor != NULL ? yoml__strdup(anchor) : NULL; 74 | node->tag = tag != NULL ? yoml__strdup(tag) : NULL; 75 | node->_refcnt = 1; 76 | node->_merged = 0; 77 | return node; 78 | } 79 | 80 | static inline yoml_t *yoml__parse_sequence(yaml_parser_t *parser, yaml_event_t *event, yoml_parse_args_t *parse_args) 81 | { 82 | yoml_t *seq = yoml__new_node(parse_args->filename, YOML_TYPE_SEQUENCE, offsetof(yoml_t, data.sequence.elements), 83 | event->data.sequence_start.anchor, event->data.sequence_start.tag, event->start_mark.line, 84 | event->start_mark.column); 85 | 86 | seq->data.sequence.size = 0; 87 | 88 | while (1) { 89 | yoml_t *new_node; 90 | yaml_event_type_t unhandled; 91 | if ((new_node = yoml__parse_node(parser, &unhandled, parse_args)) == NULL) { 92 | if (unhandled == YAML_SEQUENCE_END_EVENT) { 93 | break; 94 | } else { 95 | yoml_free(seq, parse_args->mem_set); 96 | seq = NULL; 97 | break; 98 | } 99 | } 100 | seq = realloc(seq, offsetof(yoml_t, data.sequence.elements) + sizeof(yoml_t *) * (seq->data.sequence.size + 1)); 101 | seq->data.sequence.elements[seq->data.sequence.size++] = new_node; 102 | } 103 | 104 | return seq; 105 | } 106 | 107 | static inline yoml_t *yoml__parse_mapping(yaml_parser_t *parser, yaml_event_t *event, yoml_parse_args_t *parse_args) 108 | { 109 | yoml_t *map = yoml__new_node(parse_args->filename, YOML_TYPE_MAPPING, offsetof(yoml_t, data.mapping.elements), 110 | event->data.mapping_start.anchor, event->data.mapping_start.tag, event->start_mark.line, 111 | event->start_mark.column); 112 | 113 | map->data.mapping.size = 0; 114 | 115 | while (1) { 116 | yoml_t *key, *value; 117 | yaml_event_type_t unhandled; 118 | if ((key = yoml__parse_node(parser, &unhandled, parse_args)) == NULL) { 119 | if (unhandled == YAML_MAPPING_END_EVENT) { 120 | break; 121 | } else { 122 | yoml_free(map, parse_args->mem_set); 123 | map = NULL; 124 | break; 125 | } 126 | } 127 | if ((value = yoml__parse_node(parser, NULL, parse_args)) == NULL) { 128 | yoml_free(key, parse_args->mem_set); 129 | yoml_free(map, parse_args->mem_set); 130 | map = NULL; 131 | break; 132 | } 133 | map = realloc(map, offsetof(yoml_t, data.mapping.elements) + sizeof(yoml_mapping_element_t) * (map->data.mapping.size + 1)); 134 | map->data.mapping.elements[map->data.mapping.size].key = key; 135 | map->data.mapping.elements[map->data.mapping.size].value = value; 136 | ++map->data.mapping.size; 137 | } 138 | 139 | return map; 140 | } 141 | 142 | static yoml_t *yoml__parse_node(yaml_parser_t *parser, yaml_event_type_t *unhandled, yoml_parse_args_t *parse_args) 143 | { 144 | yoml_t *node; 145 | yaml_event_t event; 146 | 147 | if (unhandled != NULL) 148 | *unhandled = YAML_NO_EVENT; 149 | 150 | /* wait for a node that is not a stream/doc start event */ 151 | while (1) { 152 | if (!yaml_parser_parse(parser, &event)) 153 | return NULL; 154 | if (!(event.type == YAML_STREAM_START_EVENT || event.type == YAML_DOCUMENT_START_EVENT)) 155 | break; 156 | yaml_event_delete(&event); 157 | } 158 | 159 | switch (event.type) { 160 | case YAML_ALIAS_EVENT: 161 | node = yoml__new_node(parse_args->filename, YOML__TYPE_UNRESOLVED_ALIAS, sizeof(*node), NULL, NULL, event.start_mark.line, 162 | event.start_mark.column); 163 | node->data.alias = yoml__strdup(event.data.alias.anchor); 164 | break; 165 | case YAML_SCALAR_EVENT: 166 | node = yoml__new_node(parse_args->filename, YOML_TYPE_SCALAR, sizeof(*node), event.data.scalar.anchor, 167 | event.data.scalar.tag, yoml__scalar_get_line(&event), event.start_mark.column); 168 | node->data.scalar = yoml__strdup(event.data.scalar.value); 169 | if (parse_args->mem_set != NULL) 170 | parse_args->mem_set(event.data.scalar.value, 'A', strlen(node->data.scalar)); 171 | break; 172 | case YAML_SEQUENCE_START_EVENT: 173 | node = yoml__parse_sequence(parser, &event, parse_args); 174 | break; 175 | case YAML_MAPPING_START_EVENT: 176 | node = yoml__parse_mapping(parser, &event, parse_args); 177 | break; 178 | default: 179 | node = NULL; 180 | if (unhandled != NULL) 181 | *unhandled = event.type; 182 | break; 183 | } 184 | 185 | yaml_event_delete(&event); 186 | 187 | return node; 188 | } 189 | 190 | static inline int yoml__merge(yoml_t **dest, size_t offset, size_t delete_count, yoml_t *src, yoml_parse_args_t *parse_args) 191 | { 192 | assert(offset + delete_count <= (*dest)->data.mapping.size); 193 | 194 | if (src->type != YOML_TYPE_MAPPING) 195 | return -1; 196 | 197 | /* create new node, copy attributes and elements of `*dest` up to `offset` */ 198 | yoml_t *new_node = yoml__new_node((*dest)->filename, (*dest)->type, 199 | offsetof(yoml_t, data.mapping.elements) + 200 | ((*dest)->data.mapping.size + src->data.mapping.size - delete_count) * 201 | sizeof(new_node->data.mapping.elements[0]), 202 | (void *)(*dest)->anchor, (void *)(*dest)->tag, (*dest)->line, (*dest)->column); 203 | new_node->_merged = (*dest)->_merged; 204 | memcpy(new_node->data.mapping.elements, (*dest)->data.mapping.elements, offset * sizeof(new_node->data.mapping.elements[0])); 205 | new_node->data.mapping.size = offset; 206 | 207 | /* copy elements from `src`, ignoring the ones that are defined later in `*dest` */ 208 | for (size_t i = 0; i != src->data.mapping.size; ++i) { 209 | yoml_mapping_element_t *src_element = src->data.mapping.elements + i; 210 | if (src_element->key->type == YOML_TYPE_SCALAR) { 211 | for (size_t j = offset + delete_count; j != (*dest)->data.mapping.size; ++j) { 212 | if ((*dest)->data.mapping.elements[j].key->type == YOML_TYPE_SCALAR && 213 | strcmp((*dest)->data.mapping.elements[j].key->data.scalar, src_element->key->data.scalar) == 0) 214 | goto Skip; 215 | } 216 | } 217 | new_node->data.mapping.elements[new_node->data.mapping.size++] = *src_element; 218 | Skip:; 219 | } 220 | 221 | /* copy elements of `*dest` after `offset + delete_count` */ 222 | memcpy(new_node->data.mapping.elements + new_node->data.mapping.size, (*dest)->data.mapping.elements + offset + delete_count, 223 | ((*dest)->data.mapping.size - offset - delete_count) * sizeof(new_node->data.mapping.elements[0])); 224 | new_node->data.mapping.size += (*dest)->data.mapping.size - offset - delete_count; 225 | 226 | /* increment the reference counters of the elements being added to the newly 227 | * created node */ 228 | for (size_t i = 0; i != new_node->data.mapping.size; ++i) { 229 | ++new_node->data.mapping.elements[i].key->_refcnt; 230 | ++new_node->data.mapping.elements[i].value->_refcnt; 231 | } 232 | 233 | /* replace `*dest` with `new_node` */ 234 | yoml_free(*dest, parse_args->mem_set); 235 | *dest = new_node; 236 | 237 | return 0; 238 | } 239 | 240 | static inline int yoml__resolve_merge(yoml_t **target, yaml_parser_t *parser, yoml_parse_args_t *parse_args) 241 | { 242 | size_t i, j; 243 | 244 | if ((*target)->_merged) 245 | return 0; 246 | (*target)->_merged = 1; 247 | 248 | switch ((*target)->type) { 249 | case YOML_TYPE_SCALAR: 250 | break; 251 | case YOML_TYPE_SEQUENCE: 252 | for (i = 0; i != (*target)->data.sequence.size; ++i) { 253 | if (yoml__resolve_merge((*target)->data.sequence.elements + i, parser, parse_args) != 0) 254 | return -1; 255 | } 256 | break; 257 | case YOML_TYPE_MAPPING: 258 | if ((*target)->data.mapping.size != 0) { 259 | i = (*target)->data.mapping.size; 260 | do { 261 | --i; 262 | if (yoml__resolve_merge(&(*target)->data.mapping.elements[i].key, parser, parse_args) != 0) 263 | return -1; 264 | if (yoml__resolve_merge(&(*target)->data.mapping.elements[i].value, parser, parse_args) != 0) 265 | return -1; 266 | if ((*target)->data.mapping.elements[i].key->type == YOML_TYPE_SCALAR && 267 | strcmp((*target)->data.mapping.elements[i].key->data.scalar, "<<") == 0) { 268 | yoml_mapping_element_t src = (*target)->data.mapping.elements[i]; 269 | /* merge */ 270 | if (src.value->type == YOML_TYPE_SEQUENCE) { 271 | for (j = 0; j != src.value->data.sequence.size; ++j) 272 | if (yoml__merge(target, i, j == 0, src.value->data.sequence.elements[j], parse_args) != 0) { 273 | MergeError: 274 | if (parser != NULL) { 275 | parser->problem = "value of the merge key MUST be a mapping or a sequence of mappings"; 276 | parser->problem_mark.line = src.key->line; 277 | parser->problem_mark.column = src.key->column; 278 | } 279 | return -1; 280 | } 281 | } else { 282 | if (yoml__merge(target, i, 1, src.value, parse_args) != 0) 283 | goto MergeError; 284 | } 285 | } 286 | } while (i != 0); 287 | } 288 | break; 289 | case YOML__TYPE_UNRESOLVED_ALIAS: 290 | assert(!"unreachable"); 291 | break; 292 | } 293 | 294 | return 0; 295 | } 296 | 297 | static inline int yoml__resolve_alias(yoml_t **target, yoml_t *doc, yaml_parser_t *parser, yoml_parse_args_t *parse_args) 298 | { 299 | size_t i; 300 | 301 | switch ((*target)->type) { 302 | case YOML_TYPE_SCALAR: 303 | break; 304 | case YOML_TYPE_SEQUENCE: 305 | for (i = 0; i != (*target)->data.sequence.size; ++i) { 306 | if (yoml__resolve_alias((*target)->data.sequence.elements + i, doc, parser, parse_args) != 0) 307 | return -1; 308 | } 309 | break; 310 | case YOML_TYPE_MAPPING: 311 | for (i = 0; i != (*target)->data.mapping.size; ++i) { 312 | if (yoml__resolve_alias(&(*target)->data.mapping.elements[i].key, doc, parser, parse_args) != 0) 313 | return -1; 314 | if (yoml__resolve_alias(&(*target)->data.mapping.elements[i].value, doc, parser, parse_args) != 0) 315 | return -1; 316 | } 317 | break; 318 | case YOML__TYPE_UNRESOLVED_ALIAS: { 319 | yoml_t *node = yoml_find_anchor(doc, (*target)->data.alias); 320 | if (node == NULL) { 321 | if (parser != NULL) { 322 | parser->problem = "could not resolve the alias"; 323 | parser->problem_mark.line = (*target)->line; 324 | parser->problem_mark.column = (*target)->column; 325 | } 326 | return -1; 327 | } 328 | yoml_free(*target, parse_args->mem_set); 329 | *target = node; 330 | ++node->_refcnt; 331 | } break; 332 | } 333 | 334 | return 0; 335 | } 336 | 337 | static inline int yoml__resolve_tag(yoml_t **target, yaml_parser_t *parser, yoml_parse_args_t *parse_args) 338 | { 339 | size_t i; 340 | 341 | if (parse_args->resolve_tag.cb == NULL) 342 | return 0; 343 | 344 | if ((*target)->tag != NULL) { 345 | yoml_t *resolved = parse_args->resolve_tag.cb((*target)->tag, *target, parse_args->resolve_tag.cb_arg); 346 | if (resolved == NULL) { 347 | if (parser != NULL) { 348 | parser->problem = "tag resolution failed"; 349 | parser->problem_mark.line = (*target)->line; 350 | parser->problem_mark.column = (*target)->column; 351 | } 352 | return -1; 353 | } 354 | yoml_free(*target, parse_args->mem_set); 355 | *target = resolved; 356 | } 357 | 358 | switch ((*target)->type) { 359 | case YOML_TYPE_SCALAR: 360 | break; 361 | case YOML_TYPE_SEQUENCE: 362 | for (i = 0; i != (*target)->data.sequence.size; ++i) { 363 | if (yoml__resolve_tag((*target)->data.sequence.elements + i, parser, parse_args) != 0) 364 | return -1; 365 | } 366 | break; 367 | case YOML_TYPE_MAPPING: 368 | for (i = 0; i != (*target)->data.mapping.size; ++i) { 369 | if (yoml__resolve_tag(&(*target)->data.mapping.elements[i].key, parser, parse_args) != 0) 370 | return -1; 371 | if (yoml__resolve_tag(&(*target)->data.mapping.elements[i].value, parser, parse_args) != 0) 372 | return -1; 373 | } 374 | break; 375 | case YOML__TYPE_UNRESOLVED_ALIAS: 376 | break; 377 | } 378 | 379 | return 0; 380 | } 381 | 382 | static inline yoml_t *yoml_parse_document(yaml_parser_t *parser, yaml_event_type_t *unhandled, yoml_parse_args_t *parse_args) 383 | { 384 | yoml_t *doc; 385 | 386 | /* parse */ 387 | if ((doc = yoml__parse_node(parser, unhandled, parse_args)) == NULL) { 388 | return NULL; 389 | } 390 | if (unhandled != NULL) 391 | *unhandled = YAML_NO_EVENT; 392 | 393 | /* resolve tags, aliases and merge */ 394 | if (yoml__resolve_tag(&doc, parser, parse_args) != 0) 395 | goto Error; 396 | if (parse_args->resolve_alias && yoml__resolve_alias(&doc, doc, parser, parse_args) != 0) 397 | goto Error; 398 | if (parse_args->resolve_merge && yoml__resolve_merge(&doc, parser, parse_args) != 0) 399 | goto Error; 400 | 401 | return doc; 402 | 403 | Error: 404 | yoml_free(doc, parse_args->mem_set); 405 | return NULL; 406 | } 407 | 408 | #ifdef __cplusplus 409 | } 410 | #endif 411 | 412 | #endif 413 | --------------------------------------------------------------------------------