xref: /petsc/src/sys/objects/optionsyaml.c (revision 53efea5c680dffccb0b7659435e1dc1aa9e95c12)
1 #include <petsc/private/petscimpl.h>        /*I  "petscsys.h"   I*/
2 #if defined(PETSC_HAVE_YAML)
3 #include <yaml.h>
4 #endif
5 
6 static MPI_Comm petsc_yaml_comm = MPI_COMM_NULL; /* only used for parallel error handling */
7 
8 PETSC_STATIC_INLINE MPI_Comm PetscYAMLGetComm(void)
9 {
10   return PetscLikely(petsc_yaml_comm != MPI_COMM_NULL) ? petsc_yaml_comm : (petsc_yaml_comm = PETSC_COMM_SELF);
11 }
12 
13 PETSC_STATIC_INLINE MPI_Comm PetscYAMLSetComm(MPI_Comm comm)
14 {
15   MPI_Comm prev = PetscYAMLGetComm(); petsc_yaml_comm = comm; return prev;
16 }
17 
18 #if defined(PETSC_HAVE_YAML)
19 
20 #define TAG(node) ((const char *)((node)->tag))
21 #define STR(node) ((const char *)((node)->data.scalar.value))
22 #define SEQ(node) ((node)->data.sequence.items)
23 #define MAP(node) ((node)->data.mapping.pairs)
24 
25 static PetscErrorCode PetscParseLayerYAML(PetscOptions options, yaml_document_t *doc, yaml_node_t *node)
26 {
27   MPI_Comm         comm = PetscYAMLGetComm();
28   char             name[PETSC_MAX_OPTION_NAME] = "", prefix[PETSC_MAX_OPTION_NAME] = "";
29   PetscErrorCode   ierr;
30 
31   PetscFunctionBegin;
32   if (node->type != YAML_MAPPING_NODE) SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected mapping");
33   for (yaml_node_pair_t *pair = MAP(node).start; pair < MAP(node).top; pair++) {
34     yaml_node_t *keynode = yaml_document_get_node(doc, pair->key);
35     yaml_node_t *valnode = yaml_document_get_node(doc, pair->value);
36     PetscBool   isMergeKey,isDummyKey,isIncludeTag;
37 
38     if (!keynode) SETERRQ(comm, PETSC_ERR_LIB, "Corrupt YAML document");
39     if (!valnode) SETERRQ(comm, PETSC_ERR_LIB, "Corrupt YAML document");
40     if (keynode->type != YAML_SCALAR_NODE) SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar");
41 
42     /* "<<" is the merge key: don't increment the prefix */
43     ierr = PetscStrcmp(STR(keynode), "<<", &isMergeKey);CHKERRQ(ierr);
44     if (isMergeKey) {
45       if (valnode->type == YAML_SEQUENCE_NODE) {
46         for (yaml_node_item_t *item = SEQ(valnode).start; item < SEQ(valnode).top; item++) {
47           yaml_node_t *itemnode = yaml_document_get_node(doc, *item);
48           if (!itemnode) SETERRQ(comm, PETSC_ERR_LIB, "Corrupt YAML document");
49           if (itemnode->type != YAML_MAPPING_NODE) SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected mapping");
50           ierr = PetscParseLayerYAML(options, doc, itemnode);CHKERRQ(ierr);
51         }
52       } else if (valnode->type == YAML_MAPPING_NODE) {
53         ierr = PetscParseLayerYAML(options, doc, valnode);CHKERRQ(ierr);
54       } else SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected sequence or mapping");
55       continue; /* to next pair */
56     }
57 
58     /* "$$*" are treated as dummy keys, we use them for !include tags and to define anchors */
59     ierr = PetscStrbeginswith(STR(keynode), "$$", &isDummyKey);CHKERRQ(ierr);
60     if (isDummyKey) {
61       ierr = PetscStrendswith(TAG(valnode), "!include", &isIncludeTag);CHKERRQ(ierr);CHKERRQ(ierr);
62       if (isIncludeTag) { /* TODO: add proper support relative paths */
63         ierr = PetscOptionsInsertFileYAML(comm, options, STR(valnode), PETSC_TRUE);CHKERRQ(ierr);
64       }
65       continue; /* to next pair */
66     }
67 
68     if (valnode->type == YAML_SCALAR_NODE) {
69       ierr = PetscSNPrintf(name, sizeof(name), "-%s", STR(keynode));CHKERRQ(ierr);
70       ierr = PetscOptionsSetValue(options, name, STR(valnode));CHKERRQ(ierr);
71 
72     } else if (valnode->type == YAML_SEQUENCE_NODE) {
73       PetscSegBuffer seg;
74       char           *buf, *strlist;
75       PetscBool      addSep = PETSC_FALSE;
76 
77       ierr = PetscSegBufferCreate(sizeof(char), PETSC_MAX_PATH_LEN, &seg);CHKERRQ(ierr);
78       for (yaml_node_item_t *item = SEQ(valnode).start; item < SEQ(valnode).top; item++) {
79         yaml_node_t *itemnode = yaml_document_get_node(doc, *item);
80         const char  *itemstr = NULL;
81         size_t       itemlen;
82 
83         if (!itemnode) SETERRQ(comm, PETSC_ERR_LIB, "Corrupt YAML document");
84 
85         if (itemnode->type == YAML_SCALAR_NODE) {
86           itemstr = STR(itemnode);
87 
88         } else if (itemnode->type == YAML_MAPPING_NODE) {
89           yaml_node_pair_t *kvn = itemnode->data.mapping.pairs.start;
90           yaml_node_pair_t *top = itemnode->data.mapping.pairs.top;
91 
92           if (top - kvn > 1) SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node value: expected a single key:value pair");
93           if (top - kvn > 0) {
94             yaml_node_t *kn = yaml_document_get_node(doc, kvn->key);
95             yaml_node_t *vn = yaml_document_get_node(doc, kvn->value);
96 
97             if (!kn) SETERRQ(comm, PETSC_ERR_LIB, "Corrupt YAML document");
98             if (!vn) SETERRQ(comm, PETSC_ERR_LIB, "Corrupt YAML document");
99             if (kn->type != YAML_SCALAR_NODE) SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar");
100 
101             ierr = PetscStrcmp(STR(kn), "<<", &isMergeKey);CHKERRQ(ierr);
102             if (isMergeKey) SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node value: merge key '<<' not supported here");
103 
104             ierr = PetscStrbeginswith(STR(kn), "$$", &isDummyKey);CHKERRQ(ierr);
105             if (isDummyKey) continue;
106             itemstr = STR(kn);
107           }
108 
109           ierr = PetscSNPrintf(prefix,sizeof(prefix), "%s_", STR(keynode));CHKERRQ(ierr);
110           ierr = PetscOptionsPrefixPush(options, prefix);CHKERRQ(ierr);
111           ierr = PetscParseLayerYAML(options, doc, itemnode);CHKERRQ(ierr);
112           ierr = PetscOptionsPrefixPop(options);CHKERRQ(ierr);
113 
114         } else SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar or mapping");
115 
116         ierr = PetscStrlen(itemstr, &itemlen);CHKERRQ(ierr);
117         if (itemlen) {
118           if (addSep) {
119             ierr = PetscSegBufferGet(seg, 1, &buf);CHKERRQ(ierr);
120             ierr = PetscArraycpy(buf, ",", 1);CHKERRQ(ierr);
121           }
122           ierr = PetscSegBufferGet(seg, itemlen, &buf);CHKERRQ(ierr);
123           ierr = PetscArraycpy(buf, itemstr, itemlen);CHKERRQ(ierr);
124           addSep = PETSC_TRUE;
125         }
126       }
127       ierr = PetscSegBufferGet(seg, 1, &buf);CHKERRQ(ierr);
128       ierr = PetscArrayzero(buf, 1);CHKERRQ(ierr);
129       ierr = PetscSegBufferExtractAlloc(seg, &strlist);CHKERRQ(ierr);
130       ierr = PetscSegBufferDestroy(&seg);CHKERRQ(ierr);
131 
132       ierr = PetscSNPrintf(name, sizeof(name), "-%s", STR(keynode));CHKERRQ(ierr);
133       ierr = PetscOptionsSetValue(options, name, strlist);CHKERRQ(ierr);
134       ierr = PetscFree(strlist);CHKERRQ(ierr);
135 
136     } else if (valnode->type == YAML_MAPPING_NODE) {
137       ierr = PetscSNPrintf(prefix,sizeof(prefix), "%s_", STR(keynode));CHKERRQ(ierr);
138       ierr = PetscOptionsPrefixPush(options, prefix);CHKERRQ(ierr);
139       ierr = PetscParseLayerYAML(options, doc, valnode);CHKERRQ(ierr);
140       ierr = PetscOptionsPrefixPop(options);CHKERRQ(ierr);
141 
142     } else SETERRQ(comm, PETSC_ERR_SUP, "Unsupported YAML node type: expected scalar, sequence or mapping");
143   }
144   PetscFunctionReturn(0);
145 }
146 
147 #endif
148 
149 /*@C
150    PetscOptionsInsertStringYAML - Inserts YAML-formatted options into the database from a string
151 
152    Logically Collective
153 
154    Input Parameter:
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(), PetscOptionsHead(),
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 #if defined(PETSC_HAVE_YAML)
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 #else
191   MPI_Comm comm = PetscYAMLGetComm();
192   (void)options; (void)in_str; /* unused */
193   SETERRQ(comm, PETSC_ERR_SUP, "YAML not supported in this build.\nPlease reconfigure using --download-yaml");
194 #endif
195 }
196 
197 /*@C
198   PetscOptionsInsertFileYAML - Insert a YAML-formatted file in the option database
199 
200   Collective
201 
202   Input Parameter:
203 +   comm - the processes that will share the options (usually PETSC_COMM_WORLD)
204 .   options - options database, use NULL for default global database
205 .   file - name of file
206 -   require - if PETSC_TRUE will generate an error if the file does not exist
207 
208   Only a small subset of the YAML standard is implemented. Non-scalar keys are NOT supported;
209   aliases and the merge key "<<" are.
210   The algorithm recursively parses the yaml file, pushing and popping prefixes
211   and inserting key + values pairs using PetscOptionsSetValue().
212 
213   PETSc will generate an error condition that stops the program if a YAML error
214   is detected, hence the user should check that the YAML file is valid before
215   supplying it, for instance at http://www.yamllint.com/ .
216 
217   Inspired by https://stackoverflow.com/a/621451
218 
219   Level: intermediate
220 
221 .seealso: PetscOptionsSetValue(), PetscOptionsView(), PetscOptionsHasName(), PetscOptionsGetInt(),
222           PetscOptionsGetReal(), PetscOptionsGetString(), PetscOptionsGetIntArray(), PetscOptionsBool(),
223           PetscOptionsName(), PetscOptionsBegin(), PetscOptionsEnd(), PetscOptionsHead(),
224           PetscOptionsStringArray(),PetscOptionsRealArray(), PetscOptionsScalar(),
225           PetscOptionsBoolGroupBegin(), PetscOptionsBoolGroup(), PetscOptionsBoolGroupEnd(),
226           PetscOptionsFList(), PetscOptionsEList(), PetscOptionsInsertFile(), PetscOptionsInsertStringYAML()
227 @*/
228 PetscErrorCode PetscOptionsInsertFileYAML(MPI_Comm comm,PetscOptions options,const char file[],PetscBool require)
229 {
230   int            yamlLength = -1;
231   char          *yamlString = NULL;
232   MPI_Comm       prev;
233   PetscMPIInt    rank;
234   PetscErrorCode ierr;
235 
236   PetscFunctionBegin;
237   ierr = MPI_Comm_rank(comm, &rank);CHKERRMPI(ierr);
238   if (!rank) {
239     char   fpath[PETSC_MAX_PATH_LEN];
240     char   fname[PETSC_MAX_PATH_LEN];
241     FILE  *fd;
242     size_t rd;
243 
244     ierr = PetscStrreplace(PETSC_COMM_SELF, file, fpath, sizeof(fpath));CHKERRQ(ierr);
245     ierr = PetscFixFilename(fpath, fname);CHKERRQ(ierr);
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       if (yamlLength < 0) SETERRQ1(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to query size of YAML file: %s", fname);
253       ierr = PetscMalloc1(yamlLength+1, &yamlString);CHKERRQ(ierr);
254       rd = fread(yamlString, 1, (size_t)yamlLength, fd);
255       if (rd != (size_t)yamlLength) SETERRQ1(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   ierr = MPI_Bcast(&yamlLength, 1, MPI_INT, 0, comm);CHKERRMPI(ierr);
262   if (require && yamlLength < 0) SETERRQ1(comm, PETSC_ERR_FILE_OPEN, "Unable to open YAML option file: %s\n", file);
263   if (yamlLength < 0) PetscFunctionReturn(0);
264 
265   if (rank) {ierr = PetscMalloc1(yamlLength+1, &yamlString);CHKERRQ(ierr);}
266   ierr = MPI_Bcast(yamlString, yamlLength+1, MPI_CHAR, 0, comm);CHKERRMPI(ierr);
267 
268   prev = PetscYAMLSetComm(comm);
269   ierr = PetscOptionsInsertStringYAML(options, yamlString);CHKERRQ(ierr);
270   (void) PetscYAMLSetComm(prev);
271 
272   ierr = PetscFree(yamlString);CHKERRQ(ierr);
273   PetscFunctionReturn(0);
274 }
275