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