xref: /petsc/src/sys/objects/optionsyaml.c (revision 73dd8735585739f5039464ecfb612b6c0bd1dba5)
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