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