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