1 #define PETSC_DESIRE_FEATURE_TEST_MACROS /* for strdup() */ 2 #include <petsc/private/petscimpl.h> /*I "petscsys.h" I*/ 3 4 #if defined(PETSC_HAVE_YAML) 5 #include <yaml.h> /* use external LibYAML */ 6 #else 7 #include <../src/sys/yaml/include/yaml.h> 8 #endif 9 10 static MPI_Comm petsc_yaml_comm = MPI_COMM_NULL; /* only used for parallel error handling */ 11 12 static inline MPI_Comm PetscYAMLGetComm(void) 13 { 14 return PetscLikely(petsc_yaml_comm != MPI_COMM_NULL) ? petsc_yaml_comm : (petsc_yaml_comm = PETSC_COMM_SELF); 15 } 16 17 static inline MPI_Comm PetscYAMLSetComm(MPI_Comm comm) 18 { 19 MPI_Comm prev = PetscYAMLGetComm(); 20 petsc_yaml_comm = comm; 21 return prev; 22 } 23 24 #define TAG(node) ((const char *)((node)->tag)) 25 #define STR(node) ((const char *)((node)->data.scalar.value)) 26 #define SEQ(node) ((node)->data.sequence.items) 27 #define MAP(node) ((node)->data.mapping.pairs) 28 29 static PetscErrorCode PetscParseLayerYAML(PetscOptions options, yaml_document_t *doc, yaml_node_t *node) 30 { 31 MPI_Comm comm = PetscYAMLGetComm(); 32 char name[PETSC_MAX_OPTION_NAME] = "", prefix[PETSC_MAX_OPTION_NAME] = ""; 33 34 PetscFunctionBegin; 35 if (node->type == YAML_SCALAR_NODE && !STR(node)[0]) PetscFunctionReturn(0); /* empty */ 36 PetscCheck(node->type == YAML_MAPPING_NODE, comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected mapping"); 37 for (yaml_node_pair_t *pair = MAP(node).start; pair < MAP(node).top; pair++) { 38 yaml_node_t *keynode = yaml_document_get_node(doc, pair->key); 39 yaml_node_t *valnode = yaml_document_get_node(doc, pair->value); 40 PetscBool isMergeKey, isDummyKey, isIncludeTag; 41 42 PetscCheck(keynode, comm, PETSC_ERR_LIB, "Corrupt YAML document"); 43 PetscCheck(valnode, comm, PETSC_ERR_LIB, "Corrupt YAML document"); 44 PetscCheck(keynode->type == YAML_SCALAR_NODE, comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar"); 45 46 /* "<<" is the merge key: don't increment the prefix */ 47 PetscCall(PetscStrcmp(STR(keynode), "<<", &isMergeKey)); 48 if (isMergeKey) { 49 if (valnode->type == YAML_SEQUENCE_NODE) { 50 for (yaml_node_item_t *item = SEQ(valnode).start; item < SEQ(valnode).top; item++) { 51 yaml_node_t *itemnode = yaml_document_get_node(doc, *item); 52 PetscCheck(itemnode, comm, PETSC_ERR_LIB, "Corrupt YAML document"); 53 PetscCheck(itemnode->type == YAML_MAPPING_NODE, comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected mapping"); 54 PetscCall(PetscParseLayerYAML(options, doc, itemnode)); 55 } 56 } else if (valnode->type == YAML_MAPPING_NODE) { 57 PetscCall(PetscParseLayerYAML(options, doc, valnode)); 58 } else SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected sequence or mapping"); 59 continue; /* to next pair */ 60 } 61 62 /* "$$*" are treated as dummy keys, we use them for !include tags and to define anchors */ 63 PetscCall(PetscStrbeginswith(STR(keynode), "$$", &isDummyKey)); 64 if (isDummyKey) { 65 PetscCall(PetscStrendswith(TAG(valnode), "!include", &isIncludeTag)); 66 if (isIncludeTag) { /* TODO: add proper support relative paths */ 67 PetscCall(PetscOptionsInsertFileYAML(comm, options, STR(valnode), PETSC_TRUE)); 68 } 69 continue; /* to next pair */ 70 } 71 72 if (valnode->type == YAML_SCALAR_NODE) { 73 PetscCall(PetscSNPrintf(name, sizeof(name), "-%s", STR(keynode))); 74 PetscCall(PetscOptionsSetValue(options, name, STR(valnode))); 75 76 } else if (valnode->type == YAML_SEQUENCE_NODE) { 77 PetscSegBuffer seg; 78 char *buf, *strlist; 79 PetscBool addSep = PETSC_FALSE; 80 81 PetscCall(PetscSegBufferCreate(sizeof(char), PETSC_MAX_PATH_LEN, &seg)); 82 for (yaml_node_item_t *item = SEQ(valnode).start; item < SEQ(valnode).top; item++) { 83 yaml_node_t *itemnode = yaml_document_get_node(doc, *item); 84 const char *itemstr = NULL; 85 size_t itemlen; 86 87 PetscCheck(itemnode, comm, PETSC_ERR_LIB, "Corrupt YAML document"); 88 89 if (itemnode->type == YAML_SCALAR_NODE) { 90 itemstr = STR(itemnode); 91 92 } else if (itemnode->type == YAML_MAPPING_NODE) { 93 yaml_node_pair_t *kvn = itemnode->data.mapping.pairs.start; 94 yaml_node_pair_t *top = itemnode->data.mapping.pairs.top; 95 96 PetscCheck(top - kvn <= 1, comm, PETSC_ERR_SUP, "Unsupported YAML node value: expected a single key:value pair"); 97 if (top - kvn > 0) { 98 yaml_node_t *kn = yaml_document_get_node(doc, kvn->key); 99 yaml_node_t *vn = yaml_document_get_node(doc, kvn->value); 100 101 PetscCheck(kn, comm, PETSC_ERR_LIB, "Corrupt YAML document"); 102 PetscCheck(vn, comm, PETSC_ERR_LIB, "Corrupt YAML document"); 103 PetscCheck(kn->type == YAML_SCALAR_NODE, comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar"); 104 105 PetscCall(PetscStrcmp(STR(kn), "<<", &isMergeKey)); 106 PetscCheck(!isMergeKey, comm, PETSC_ERR_SUP, "Unsupported YAML node value: merge key '<<' not supported here"); 107 108 PetscCall(PetscStrbeginswith(STR(kn), "$$", &isDummyKey)); 109 if (isDummyKey) continue; 110 itemstr = STR(kn); 111 } 112 113 PetscCall(PetscSNPrintf(prefix, sizeof(prefix), "%s_", STR(keynode))); 114 PetscCall(PetscOptionsPrefixPush(options, prefix)); 115 PetscCall(PetscParseLayerYAML(options, doc, itemnode)); 116 PetscCall(PetscOptionsPrefixPop(options)); 117 118 } else SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar or mapping"); 119 120 PetscCall(PetscStrlen(itemstr, &itemlen)); 121 if (itemlen) { 122 if (addSep) { 123 PetscCall(PetscSegBufferGet(seg, 1, &buf)); 124 PetscCall(PetscArraycpy(buf, ",", 1)); 125 } 126 PetscCall(PetscSegBufferGet(seg, itemlen, &buf)); 127 PetscCall(PetscArraycpy(buf, itemstr, itemlen)); 128 addSep = PETSC_TRUE; 129 } 130 } 131 PetscCall(PetscSegBufferGet(seg, 1, &buf)); 132 PetscCall(PetscArrayzero(buf, 1)); 133 PetscCall(PetscSegBufferExtractAlloc(seg, &strlist)); 134 PetscCall(PetscSegBufferDestroy(&seg)); 135 136 PetscCall(PetscSNPrintf(name, sizeof(name), "-%s", STR(keynode))); 137 PetscCall(PetscOptionsSetValue(options, name, strlist)); 138 PetscCall(PetscFree(strlist)); 139 140 } else if (valnode->type == YAML_MAPPING_NODE) { 141 PetscCall(PetscSNPrintf(prefix, sizeof(prefix), "%s_", STR(keynode))); 142 PetscCall(PetscOptionsPrefixPush(options, prefix)); 143 PetscCall(PetscParseLayerYAML(options, doc, valnode)); 144 PetscCall(PetscOptionsPrefixPop(options)); 145 146 } else SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar, sequence or mapping"); 147 } 148 PetscFunctionReturn(0); 149 } 150 151 /*@C 152 PetscOptionsInsertStringYAML - Inserts YAML-formatted options into the options database from a string 153 154 Logically Collective 155 156 Input Parameters: 157 + options - options database, use NULL for default global database 158 - in_str - YAML-formatted string options 159 160 Level: intermediate 161 162 .seealso: `PetscOptionsSetValue()`, `PetscOptionsView()`, `PetscOptionsHasName()`, `PetscOptionsGetInt()`, 163 `PetscOptionsGetReal()`, `PetscOptionsGetString()`, `PetscOptionsGetIntArray()`, `PetscOptionsBool()`, 164 `PetscOptionsName()`, `PetscOptionsBegin()`, `PetscOptionsEnd()`, `PetscOptionsHeadBegin()`, 165 `PetscOptionsStringArray()`, `PetscOptionsRealArray()`, `PetscOptionsScalar()`, 166 `PetscOptionsBoolGroupBegin()`, `PetscOptionsBoolGroup()`, `PetscOptionsBoolGroupEnd()`, 167 `PetscOptionsFList()`, `PetscOptionsEList()`, `PetscOptionsInsertFile()`, `PetscOptionsInsertFileYAML()` 168 @*/ 169 PetscErrorCode PetscOptionsInsertStringYAML(PetscOptions options, const char in_str[]) 170 { 171 MPI_Comm comm = PetscYAMLGetComm(); 172 yaml_parser_t parser; 173 yaml_document_t doc; 174 yaml_node_t *root; 175 int err; 176 177 PetscFunctionBegin; 178 if (!in_str) in_str = ""; 179 err = !yaml_parser_initialize(&parser); 180 PetscCheck(!err, comm, PETSC_ERR_LIB, "YAML parser initialization error"); 181 yaml_parser_set_input_string(&parser, (const unsigned char *)in_str, strlen(in_str)); 182 do { 183 err = !yaml_parser_load(&parser, &doc); 184 PetscCheck(!err, comm, PETSC_ERR_LIB, "YAML parser loading error"); 185 root = yaml_document_get_root_node(&doc); 186 if (root) PetscCall(PetscParseLayerYAML(options, &doc, root)); 187 yaml_document_delete(&doc); 188 } while (root); 189 yaml_parser_delete(&parser); 190 PetscFunctionReturn(0); 191 } 192 193 /*@C 194 PetscOptionsInsertFileYAML - Insert a YAML-formatted file in the options database 195 196 Collective 197 198 Input Parameters: 199 + comm - the processes that will share the options (usually `PETSC_COMM_WORLD`) 200 . options - options database, use NULL for default global database 201 . file - name of file 202 - require - if `PETSC_TRUE` will generate an error if the file does not exist 203 204 Notes: 205 PETSc will generate an error condition that stops the program if a YAML error 206 is detected, hence the user should check that the YAML file is valid before 207 supplying it, for instance at http://www.yamllint.com/ . 208 209 Uses `PetscOptionsInsertStringYAML()`. 210 211 Level: intermediate 212 213 .seealso: `PetscOptionsSetValue()`, `PetscOptionsView()`, `PetscOptionsHasName()`, `PetscOptionsGetInt()`, 214 `PetscOptionsGetReal()`, `PetscOptionsGetString()`, `PetscOptionsGetIntArray()`, `PetscOptionsBool()`, 215 `PetscOptionsName()`, `PetscOptionsBegin()`, `PetscOptionsEnd()`, `PetscOptionsHeadBegin()`, 216 `PetscOptionsStringArray()`, `PetscOptionsRealArray()`, `PetscOptionsScalar()`, 217 `PetscOptionsBoolGroupBegin()`, `PetscOptionsBoolGroup()`, `PetscOptionsBoolGroupEnd()`, 218 `PetscOptionsFList()`, `PetscOptionsEList()`, `PetscOptionsInsertFile()`, `PetscOptionsInsertStringYAML()` 219 @*/ 220 PetscErrorCode PetscOptionsInsertFileYAML(MPI_Comm comm, PetscOptions options, const char file[], PetscBool require) 221 { 222 int yamlLength = -1; 223 char *yamlString = NULL; 224 MPI_Comm prev; 225 PetscMPIInt rank; 226 227 PetscFunctionBegin; 228 PetscCallMPI(MPI_Comm_rank(comm, &rank)); 229 if (rank == 0) { 230 char fpath[PETSC_MAX_PATH_LEN]; 231 char fname[PETSC_MAX_PATH_LEN]; 232 FILE *fd; 233 size_t rd; 234 235 PetscCall(PetscStrreplace(PETSC_COMM_SELF, file, fpath, sizeof(fpath))); 236 PetscCall(PetscFixFilename(fpath, fname)); 237 238 fd = fopen(fname, "r"); 239 if (fd) { 240 fseek(fd, 0, SEEK_END); 241 yamlLength = (int)ftell(fd); 242 fseek(fd, 0, SEEK_SET); 243 PetscCheck(yamlLength >= 0, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to query size of YAML file: %s", fname); 244 PetscCall(PetscMalloc1(yamlLength + 1, &yamlString)); 245 rd = fread(yamlString, 1, (size_t)yamlLength, fd); 246 PetscCheck(rd == (size_t)yamlLength, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Unable to read entire YAML file: %s", fname); 247 yamlString[yamlLength] = 0; 248 fclose(fd); 249 } 250 } 251 252 PetscCallMPI(MPI_Bcast(&yamlLength, 1, MPI_INT, 0, comm)); 253 PetscCheck(!require || yamlLength >= 0, comm, PETSC_ERR_FILE_OPEN, "Unable to open YAML option file: %s", file); 254 if (yamlLength < 0) PetscFunctionReturn(0); 255 256 if (rank) PetscCall(PetscMalloc1(yamlLength + 1, &yamlString)); 257 PetscCallMPI(MPI_Bcast(yamlString, yamlLength + 1, MPI_CHAR, 0, comm)); 258 259 prev = PetscYAMLSetComm(comm); 260 PetscCall(PetscOptionsInsertStringYAML(options, yamlString)); 261 (void)PetscYAMLSetComm(prev); 262 263 PetscCall(PetscFree(yamlString)); 264 PetscFunctionReturn(0); 265 } 266 267 #if !defined(PETSC_HAVE_YAML) 268 269 /* 270 #if !defined(PETSC_HAVE_STRDUP) 271 #define strdup(s) (char*)memcpy(malloc(strlen(s)+1),s,strlen(s)+1) 272 #endif 273 */ 274 275 /* Embed LibYAML in this compilation unit */ 276 #include <../src/sys/yaml/src/api.c> 277 #include <../src/sys/yaml/src/loader.c> 278 #include <../src/sys/yaml/src/parser.c> 279 #include <../src/sys/yaml/src/reader.c> 280 281 /* 282 Avoid compiler warnings like 283 scanner.c, line 3181: warning: integer conversion resulted in a change of sign 284 *(string.pointer++) = '\xC2'; 285 286 Once yaml fixes them, we can remove the pragmas 287 */ 288 #pragma GCC diagnostic push 289 #pragma GCC diagnostic ignored "-Wsign-conversion" 290 #include <../src/sys/yaml/src/scanner.c> 291 #pragma GCC diagnostic pop 292 293 /* Silence a few unused-function warnings */ 294 static PETSC_UNUSED void petsc_yaml_unused(void) 295 { 296 (void)yaml_parser_scan; 297 (void)yaml_document_get_node; 298 (void)yaml_parser_set_encoding; 299 (void)yaml_parser_set_input; 300 (void)yaml_parser_set_input_file; 301 } 302 303 #endif 304