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