xref: /petsc/src/sys/objects/optionsyaml.c (revision 21e3ffae2f3b73c0bd738cf6d0a809700fc04bb0)
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 
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 
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 
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 
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 /*@C
178    PetscOptionsInsertStringYAML - Inserts YAML-formatted options into the options database from a string
179 
180    Logically Collective
181 
182    Input Parameters:
183 +  options - options database, use NULL for default global database
184 -  in_str - YAML-formatted string options
185 
186    Level: intermediate
187 
188 .seealso: `PetscOptionsSetValue()`, `PetscOptionsView()`, `PetscOptionsHasName()`, `PetscOptionsGetInt()`,
189           `PetscOptionsGetReal()`, `PetscOptionsGetString()`, `PetscOptionsGetIntArray()`, `PetscOptionsBool()`,
190           `PetscOptionsName()`, `PetscOptionsBegin()`, `PetscOptionsEnd()`, `PetscOptionsHeadBegin()`,
191           `PetscOptionsStringArray()`, `PetscOptionsRealArray()`, `PetscOptionsScalar()`,
192           `PetscOptionsBoolGroupBegin()`, `PetscOptionsBoolGroup()`, `PetscOptionsBoolGroupEnd()`,
193           `PetscOptionsFList()`, `PetscOptionsEList()`, `PetscOptionsInsertFile()`, `PetscOptionsInsertFileYAML()`
194 @*/
195 PetscErrorCode PetscOptionsInsertStringYAML(PetscOptions options, const char in_str[])
196 {
197   PetscFunctionBegin;
198   PetscCall(PetscOptionsInsertStringYAML_Private(options, in_str, PETSC_OPT_CODE));
199   PetscFunctionReturn(PETSC_SUCCESS);
200 }
201 
202 /*@C
203   PetscOptionsInsertFileYAML - Insert a YAML-formatted file in the options database
204 
205   Collective
206 
207   Input Parameters:
208 +   comm - the processes that will share the options (usually `PETSC_COMM_WORLD`)
209 .   options - options database, use NULL for default global database
210 .   file - name of file
211 -   require - if `PETSC_TRUE` will generate an error if the file does not exist
212 
213   Notes:
214   PETSc will generate an error condition that stops the program if a YAML error
215   is detected, hence the user should check that the YAML file is valid before
216   supplying it, for instance at http://www.yamllint.com/ .
217 
218   Uses `PetscOptionsInsertStringYAML()`.
219 
220   Level: intermediate
221 
222 .seealso: `PetscOptionsSetValue()`, `PetscOptionsView()`, `PetscOptionsHasName()`, `PetscOptionsGetInt()`,
223           `PetscOptionsGetReal()`, `PetscOptionsGetString()`, `PetscOptionsGetIntArray()`, `PetscOptionsBool()`,
224           `PetscOptionsName()`, `PetscOptionsBegin()`, `PetscOptionsEnd()`, `PetscOptionsHeadBegin()`,
225           `PetscOptionsStringArray()`, `PetscOptionsRealArray()`, `PetscOptionsScalar()`,
226           `PetscOptionsBoolGroupBegin()`, `PetscOptionsBoolGroup()`, `PetscOptionsBoolGroupEnd()`,
227           `PetscOptionsFList()`, `PetscOptionsEList()`, `PetscOptionsInsertFile()`, `PetscOptionsInsertStringYAML()`
228 @*/
229 PetscErrorCode PetscOptionsInsertFileYAML(MPI_Comm comm, PetscOptions options, const char file[], PetscBool require)
230 {
231   int         yamlLength = -1;
232   char       *yamlString = NULL;
233   MPI_Comm    prev;
234   PetscMPIInt rank;
235 
236   PetscFunctionBegin;
237   PetscCallMPI(MPI_Comm_rank(comm, &rank));
238   if (rank == 0) {
239     char   fpath[PETSC_MAX_PATH_LEN];
240     char   fname[PETSC_MAX_PATH_LEN];
241     FILE  *fd;
242     size_t rd;
243 
244     PetscCall(PetscStrreplace(PETSC_COMM_SELF, file, fpath, sizeof(fpath)));
245     PetscCall(PetscFixFilename(fpath, fname));
246 
247     fd = fopen(fname, "r");
248     if (fd) {
249       fseek(fd, 0, SEEK_END);
250       yamlLength = (int)ftell(fd);
251       fseek(fd, 0, SEEK_SET);
252       PetscCheck(yamlLength >= 0, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to query size of YAML file: %s", fname);
253       PetscCall(PetscMalloc1(yamlLength + 1, &yamlString));
254       rd = fread(yamlString, 1, (size_t)yamlLength, fd);
255       PetscCheck(rd == (size_t)yamlLength, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Unable to read entire YAML file: %s", fname);
256       yamlString[yamlLength] = 0;
257       fclose(fd);
258     }
259   }
260 
261   PetscCallMPI(MPI_Bcast(&yamlLength, 1, MPI_INT, 0, comm));
262   PetscCheck(!require || yamlLength >= 0, comm, PETSC_ERR_FILE_OPEN, "Unable to open YAML option file: %s", file);
263   if (yamlLength < 0) PetscFunctionReturn(PETSC_SUCCESS);
264 
265   if (rank) PetscCall(PetscMalloc1(yamlLength + 1, &yamlString));
266   PetscCallMPI(MPI_Bcast(yamlString, yamlLength + 1, MPI_CHAR, 0, comm));
267 
268   prev = PetscYAMLSetComm(comm);
269   PetscCall(PetscOptionsInsertStringYAML_Private(options, yamlString, PETSC_OPT_FILE));
270   (void)PetscYAMLSetComm(prev);
271 
272   PetscCall(PetscFree(yamlString));
273   PetscFunctionReturn(PETSC_SUCCESS);
274 }
275 
276 #if !defined(PETSC_HAVE_YAML)
277 
278   /*
279 #if !defined(PETSC_HAVE_STRDUP)
280 #define strdup(s) (char*)memcpy(malloc(strlen(s)+1),s,strlen(s)+1)
281 #endif
282 */
283 
284   /* Embed LibYAML in this compilation unit */
285   #include <../src/sys/yaml/src/api.c>
286   #include <../src/sys/yaml/src/loader.c>
287   #include <../src/sys/yaml/src/parser.c>
288   #include <../src/sys/yaml/src/reader.c>
289 
290   /*
291   Avoid compiler warnings like
292     scanner.c, line 3181: warning: integer conversion resulted in a change of sign
293                           *(string.pointer++) = '\xC2';
294 
295   Once yaml fixes them, we can remove the pragmas
296 */
297   #pragma GCC diagnostic push
298   #pragma GCC diagnostic ignored "-Wsign-conversion"
299   #include <../src/sys/yaml/src/scanner.c>
300   #pragma GCC diagnostic pop
301 
302 /* Silence a few unused-function warnings */
303 static PETSC_UNUSED void petsc_yaml_unused(void)
304 {
305   (void)yaml_parser_scan;
306   (void)yaml_document_get_node;
307   (void)yaml_parser_set_encoding;
308   (void)yaml_parser_set_input;
309   (void)yaml_parser_set_input_file;
310 }
311 
312 #endif
313