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