25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 */ |
27 */ |
28 |
28 |
29 #include <string> |
29 #include <string> |
30 #include <deque> |
30 #include <vector> |
31 #include <algorithm> |
31 #include <algorithm> |
32 #include <cerrno> |
32 #include <cerrno> |
33 #include <cstdio> |
33 #include <cstdio> |
34 #include <cstdlib> |
34 #include <cstdlib> |
35 #include <cstring> |
35 #include <cstring> |
36 #include <cstdarg> |
36 #include <cstdarg> |
|
37 #include "md5.h" |
37 |
38 |
38 using std::string; |
39 using std::string; |
39 using std::deque; |
40 using std::vector; |
40 |
41 |
41 static int gLineNumber; |
42 static int LineNumber; |
42 static std::string gCurrentFile; |
43 static std::string CurrentFile; |
43 |
44 static auto const null = nullptr; |
44 // ============================================================================= |
45 |
45 // |
46 #define NAMED_ENUM_MACRO "named_enum" |
46 struct NamedEnumInfo |
|
47 { |
|
48 string name; |
|
49 deque<string> enumerators; |
|
50 }; |
|
51 |
|
52 // ============================================================================= |
|
53 // |
|
54 void SkipWhitespace (char*& cp) |
|
55 { |
|
56 while (isspace (*cp)) |
|
57 { |
|
58 if (*cp == '\n') |
|
59 gLineNumber++; |
|
60 |
|
61 ++cp; |
|
62 } |
|
63 |
|
64 if (strncmp (cp, "//", 2) == 0) |
|
65 { |
|
66 while (*(++cp) != '\n') |
|
67 ; |
|
68 |
|
69 gLineNumber++; |
|
70 SkipWhitespace (cp); |
|
71 } |
|
72 } |
|
73 |
47 |
74 // ============================================================================= |
48 // ============================================================================= |
75 // |
49 // |
76 void Error (const char* fmt, ...) |
50 void Error (const char* fmt, ...) |
77 { |
51 { |
83 throw std::string (buf); |
57 throw std::string (buf); |
84 } |
58 } |
85 |
59 |
86 // ============================================================================= |
60 // ============================================================================= |
87 // |
61 // |
|
62 struct NamedEnumInfo |
|
63 { |
|
64 string name; |
|
65 vector<string> enumerators; |
|
66 bool scoped; |
|
67 string underlyingtype; |
|
68 bool valuedefs; |
|
69 |
|
70 NamedEnumInfo() : |
|
71 scoped (false), |
|
72 valuedefs (false) {} |
|
73 |
|
74 // |
|
75 // Generate a string containing a C++ stub declaration for this enum. |
|
76 // |
|
77 string makeStub() const |
|
78 { |
|
79 return string ("enum ") |
|
80 + (scoped ? "class " : "") |
|
81 + name |
|
82 + (underlyingtype.size() ? (" : " + underlyingtype) : "") |
|
83 + ";"; |
|
84 } |
|
85 |
|
86 string enumeratorString (string const& e) const |
|
87 { |
|
88 if (scoped) |
|
89 return name + "::" + e; |
|
90 else |
|
91 return e; |
|
92 } |
|
93 }; |
|
94 |
|
95 // ============================================================================= |
|
96 // |
|
97 void Normalize (string& a) |
|
98 { |
|
99 char const* start; |
|
100 char const* end; |
|
101 |
|
102 for (start = &a.c_str()[0]; isspace (*start); ++start) |
|
103 ; |
|
104 |
|
105 for (end = start; not isspace (*end) and *end != '\0'; ++end) |
|
106 ; |
|
107 |
|
108 a = a.substr (start - a.c_str(), end - start); |
|
109 } |
|
110 |
|
111 // ============================================================================= |
|
112 // |
|
113 class OutputFile |
|
114 { |
|
115 string _buffer; |
|
116 string _md5; |
|
117 string _filepath; |
|
118 |
|
119 public: |
|
120 OutputFile (string const& filepath) : |
|
121 _filepath (filepath) |
|
122 { |
|
123 FILE* readhandle = fopen (_filepath.c_str(), "r"); |
|
124 |
|
125 if (readhandle) |
|
126 { |
|
127 char line[1024]; |
|
128 |
|
129 if (fgets (line, sizeof line, readhandle) == null) |
|
130 Error ("I/O error while reading %s", _filepath.c_str()); |
|
131 |
|
132 if (strncmp (line, "// ", 3) == 0) |
|
133 { |
|
134 // Get rid of the newline at the end |
|
135 char* cp; |
|
136 for (cp = &line[3]; *cp != '\0' and *cp != '\n'; ++cp) |
|
137 ; |
|
138 |
|
139 *cp = '\0'; |
|
140 _md5 = string (&line[3]); |
|
141 } |
|
142 |
|
143 fclose (readhandle); |
|
144 } |
|
145 } |
|
146 |
|
147 void append (char const* fmtstr, ...) |
|
148 { |
|
149 char buf[1024]; |
|
150 va_list va; |
|
151 va_start (va, fmtstr); |
|
152 vsprintf (buf, fmtstr, va); |
|
153 va_end (va); |
|
154 _buffer += buf; |
|
155 } |
|
156 |
|
157 void writeToDisk() |
|
158 { |
|
159 char checksum[33]; |
|
160 |
|
161 // See if this is necessary first. |
|
162 CalculateMD5 (reinterpret_cast<unsigned char const*> (_buffer.c_str()), |
|
163 _buffer.size(), checksum); |
|
164 checksum[32] = '\0'; |
|
165 |
|
166 if (_md5.size() and string (checksum) == _md5) |
|
167 { |
|
168 fprintf (stdout, "%s is up to date.\n", _filepath.c_str()); |
|
169 return; |
|
170 } |
|
171 |
|
172 FILE* handle = fopen (_filepath.c_str(), "w"); |
|
173 |
|
174 if (not handle) |
|
175 Error ("couldn't open %s for writing", _filepath.c_str()); |
|
176 |
|
177 string md5header (string ("// ") + checksum + "\n"); |
|
178 fwrite (md5header.c_str(), 1, md5header.size(), handle); |
|
179 fwrite (_buffer.c_str(), 1, _buffer.size(), handle); |
|
180 fclose (handle); |
|
181 fprintf (stdout, "Wrote output file %s.\n", _filepath.c_str()); |
|
182 } |
|
183 }; |
|
184 |
|
185 // ============================================================================= |
|
186 // |
|
187 void SkipWhitespace (char*& cp) |
|
188 { |
|
189 while (isspace (*cp)) |
|
190 { |
|
191 if (*cp == '\n') |
|
192 LineNumber++; |
|
193 |
|
194 ++cp; |
|
195 } |
|
196 |
|
197 if (strncmp (cp, "//", 2) == 0) |
|
198 { |
|
199 while (*(++cp) != '\n') |
|
200 ; |
|
201 |
|
202 LineNumber++; |
|
203 SkipWhitespace (cp); |
|
204 } |
|
205 } |
|
206 |
|
207 // ============================================================================= |
|
208 // |
88 int main (int argc, char* argv[]) |
209 int main (int argc, char* argv[]) |
89 { |
210 { |
90 try |
211 try |
91 { |
212 { |
92 deque<NamedEnumInfo> namedEnumerations; |
213 vector<NamedEnumInfo> namedEnumerations; |
93 deque<string> filesToInclude; |
214 vector<string> filesToInclude; |
94 |
215 |
95 if (argc < 3) |
216 if (argc < 3) |
96 { |
217 { |
97 fprintf (stderr, "usage: %s input [input [input [...]]] output\n", argv[0]); |
218 fprintf (stderr, "usage: %s input [input [input [...]]] output\n", argv[0]); |
98 return EXIT_FAILURE; |
219 return EXIT_FAILURE; |
99 } |
220 } |
100 |
221 |
101 for (int i = 1; i < argc - 1; ++ i) |
222 OutputFile header (argv[argc - 2]); |
102 { |
223 OutputFile source (argv[argc - 1]); |
103 gCurrentFile = argv[i]; |
224 |
|
225 for (int i = 1; i < argc - 2; ++ i) |
|
226 { |
104 FILE* fp = fopen (argv[i], "r"); |
227 FILE* fp = fopen (argv[i], "r"); |
105 char* buf; |
228 char* buf; |
106 gLineNumber = 1; |
|
107 |
229 |
108 if (fp == NULL) |
230 if (fp == NULL) |
109 { |
231 { |
110 fprintf (stderr, "could not open %s for writing: %s\n", |
232 CurrentFile = ""; |
|
233 Error ("could not open %s for reading: %s", |
111 argv[i], strerror (errno)); |
234 argv[i], strerror (errno)); |
112 exit (EXIT_FAILURE); |
235 } |
113 } |
236 |
114 |
237 CurrentFile = argv[i]; |
|
238 LineNumber = 1; |
115 fseek (fp, 0, SEEK_END); |
239 fseek (fp, 0, SEEK_END); |
116 long int filesize = ftell (fp); |
240 long int filesize = ftell (fp); |
117 rewind (fp); |
241 rewind (fp); |
118 |
242 |
119 try |
243 try |
120 { |
244 { |
121 buf = new char[filesize]; |
245 buf = new char[filesize]; |
122 } |
246 } |
123 catch (std::bad_alloc) |
247 catch (std::bad_alloc) |
124 { |
248 { |
125 Error ("could not allocate %ld bytes for %s\n", filesize, argv[i]); |
249 Error ("out of memory: could not allocate %ld bytes for opening %s\n", |
126 } |
250 filesize, argv[i]); |
127 |
251 } |
128 if (static_cast<long> (fread (buf, 1, filesize, fp)) < filesize) |
252 |
129 Error ("could not read %ld bytes from %s\n", filesize, argv[i]); |
253 if (long (fread (buf, 1, filesize, fp)) < filesize) |
|
254 Error ("filesystem error: could not read %ld bytes from %s\n", filesize, argv[i]); |
130 |
255 |
131 char* const end = &buf[0] + filesize; |
256 char* const end = &buf[0] + filesize; |
132 |
257 |
133 for (char* cp = &buf[0]; cp < end; ++cp) |
258 for (char* cp = &buf[0]; cp < end; ++cp) |
134 { |
259 { |
141 |
266 |
142 continue; |
267 continue; |
143 } |
268 } |
144 |
269 |
145 if ((cp != &buf[0] && isspace (* (cp - 1)) == false) || |
270 if ((cp != &buf[0] && isspace (* (cp - 1)) == false) || |
146 strncmp (cp, "named_enum ", strlen ("named_enum ")) != 0) |
271 strncmp (cp, NAMED_ENUM_MACRO " ", strlen (NAMED_ENUM_MACRO " ")) != 0) |
147 { |
272 { |
148 continue; |
273 continue; |
149 } |
274 } |
150 |
275 |
151 cp += strlen ("named_enum "); |
276 cp += strlen (NAMED_ENUM_MACRO " "); |
152 SkipWhitespace (cp); |
277 SkipWhitespace (cp); |
153 |
278 |
154 NamedEnumInfo nenum; |
279 NamedEnumInfo nenum; |
155 auto& enumname = nenum.name; |
280 auto& enumname = nenum.name; |
156 auto& enumerators = nenum.enumerators; |
281 auto& enumerators = nenum.enumerators; |
157 |
282 |
|
283 // See if it's a scoped enum |
|
284 if (strncmp (cp, "class ", strlen ("class ")) == 0) |
|
285 { |
|
286 nenum.scoped = true; |
|
287 cp += strlen ("class "); |
|
288 } |
|
289 |
158 if (isalpha (*cp) == false && *cp != '_') |
290 if (isalpha (*cp) == false && *cp != '_') |
159 Error ("anonymous named_enum"); |
291 Error ("anonymous " NAMED_ENUM_MACRO); |
160 |
292 |
161 while (isalnum (*cp) || *cp == '_') |
293 while (isalnum (*cp) || *cp == '_') |
162 enumname += *cp++; |
294 enumname += *cp++; |
163 |
295 |
164 SkipWhitespace (cp); |
296 SkipWhitespace (cp); |
165 |
297 |
|
298 // We need an underlying type if this is not a scoped enum |
|
299 if (*cp == ':') |
|
300 { |
|
301 SkipWhitespace (cp); |
|
302 |
|
303 for (++cp; *cp != '\0' and *cp != '{'; ++cp) |
|
304 nenum.underlyingtype += *cp; |
|
305 |
|
306 if (not nenum.underlyingtype.size()) |
|
307 Error ("underlying type left empty"); |
|
308 |
|
309 Normalize (nenum.underlyingtype); |
|
310 } |
|
311 |
|
312 if (not nenum.scoped and not nenum.underlyingtype.size()) |
|
313 { |
|
314 Error (NAMED_ENUM_MACRO " %s must be forward-declarable and thus must either " |
|
315 "be scoped (" NAMED_ENUM_MACRO " class) or define an underlying type " |
|
316 "(enum A : int)", nenum.name.c_str()); |
|
317 } |
|
318 |
166 if (*cp++ != '{') |
319 if (*cp++ != '{') |
167 Error ("expected '{' after named_enum"); |
320 Error ("expected '{' after " NAMED_ENUM_MACRO); |
168 |
321 |
169 for (;;) |
322 for (;;) |
170 { |
323 { |
171 SkipWhitespace (cp); |
324 SkipWhitespace (cp); |
172 |
325 |
203 if (enumerators.size() > 0) |
367 if (enumerators.size() > 0) |
204 { |
368 { |
205 namedEnumerations.push_back (nenum); |
369 namedEnumerations.push_back (nenum); |
206 filesToInclude.push_back (argv[i]); |
370 filesToInclude.push_back (argv[i]); |
207 } |
371 } |
208 } |
372 else |
209 } |
373 { |
210 |
374 printf ("warning: " NAMED_ENUM_MACRO " %s left empty\n", nenum.name.c_str()); |
211 FILE* fp; |
375 } |
212 |
376 } |
213 if ((fp = fopen (argv[argc - 1], "w")) == NULL) |
377 } |
214 Error ("couldn't open %s for writing: %s", argv[argc - 1], strerror (errno)); |
378 |
215 |
379 header.append ("#pragma once\n\n"); |
216 fprintf (fp, "#pragma once\n"); |
380 |
|
381 for (NamedEnumInfo& e : namedEnumerations) |
|
382 header.append ("%s\n", e.makeStub().c_str()); |
|
383 |
|
384 header.append ("\n"); |
|
385 |
|
386 for (NamedEnumInfo& e : namedEnumerations) |
|
387 header.append ("const char* Get%sString (%s value);\n", e.name.c_str(), e.name.c_str()); |
|
388 |
|
389 header.append ("\n"); |
|
390 |
|
391 // MakeFormatArgument overloads so enums can be passed to that |
|
392 for (NamedEnumInfo& e : namedEnumerations) |
|
393 header.append ("String MakeFormatArgument (%s value);\n", e.name.c_str()); |
217 |
394 |
218 std::sort (filesToInclude.begin(), filesToInclude.end()); |
395 std::sort (filesToInclude.begin(), filesToInclude.end()); |
219 auto pos = std::unique (filesToInclude.begin(), filesToInclude.end()); |
396 auto pos = std::unique (filesToInclude.begin(), filesToInclude.end()); |
220 filesToInclude.resize (std::distance (filesToInclude.begin(), pos)); |
397 filesToInclude.resize (std::distance (filesToInclude.begin(), pos)); |
221 |
398 |
222 for (const string& a : filesToInclude) |
399 for (string const& a : filesToInclude) |
223 fprintf (fp, "#include \"%s\"\n", basename (a.c_str())); |
400 source.append ("#include \"%s\"\n", basename (a.c_str())); |
|
401 |
|
402 source.append ("#include \"%s\"\n", basename (argv[argc - 2])); |
224 |
403 |
225 for (NamedEnumInfo& e : namedEnumerations) |
404 for (NamedEnumInfo& e : namedEnumerations) |
226 { |
405 { |
227 fprintf (fp, "\nstatic const char* g_%sNames[] =\n{\n", e.name.c_str()); |
406 if (not e.valuedefs) |
228 |
407 { |
229 for (const string& a : e.enumerators) |
408 source.append ("\nstatic const char* %sNames[] =\n{\n", e.name.c_str()); |
230 fprintf (fp, "\t\"%s\",\n", a.c_str()); |
409 |
231 |
410 for (const string& a : e.enumerators) |
232 fprintf (fp, "};\n\n"); |
411 source.append ("\t\"%s\",\n", e.enumeratorString (a).c_str()); |
233 |
412 |
234 fprintf (fp, "inline const char* get%sString (%s a)\n" |
413 source.append ("};\n\n"); |
235 "{\n" |
414 |
236 "\treturn g_%sNames[a];\n" |
415 source.append ("const char* Get%sString (%s value)\n" |
237 "}\n", |
416 "{\n" |
238 e.name.c_str(), e.name.c_str(), e.name.c_str()); |
417 "\treturn %sNames[value];\n" |
239 } |
418 "}\n", |
240 |
419 e.name.c_str(), e.name.c_str(), e.name.c_str()); |
241 printf ("Wrote named enumerations to %s\n", argv[argc - 1]); |
420 } |
242 fclose (fp); |
421 else |
|
422 { |
|
423 source.append ("const char* Get%sString (%s value)\n" |
|
424 "{\n" |
|
425 "\tswitch (value)\n" |
|
426 "\t{\n", e.name.c_str(), e.name.c_str()); |
|
427 |
|
428 for (const string& a : e.enumerators) |
|
429 { |
|
430 source.append ("\t\tcase %s: return \"%s\";\n", |
|
431 e.enumeratorString (a).c_str(), e.enumeratorString (a).c_str()); |
|
432 } |
|
433 |
|
434 source.append ("\t\tdefault: return (\"[[[unknown " |
|
435 "value passed to Get%sString]]]\");\n\t}\n}\n", e.name.c_str()); |
|
436 } |
|
437 |
|
438 source.append ("String MakeFormatArgument (%s value)\n{\n" |
|
439 "\treturn Get%sString (value);\n}\n", e.name.c_str(), e.name.c_str()); |
|
440 } |
|
441 |
|
442 source.writeToDisk(); |
|
443 header.writeToDisk(); |
243 } |
444 } |
244 catch (std::string a) |
445 catch (std::string a) |
245 { |
446 { |
246 fprintf (stderr, "%s:%d: error: %s\n", gCurrentFile.c_str(), gLineNumber, a.c_str()); |
447 if (CurrentFile.size() > 0) |
|
448 { |
|
449 fprintf (stderr, "%s: %s:%d: error: %s\n", |
|
450 argv[0], CurrentFile.c_str(), LineNumber, a.c_str()); |
|
451 } |
|
452 else |
|
453 { |
|
454 fprintf (stderr, "%s: error: %s\n", argv[0], a.c_str()); |
|
455 } |
247 return 1; |
456 return 1; |
248 } |
457 } |
249 |
458 |
250 return 0; |
459 return 0; |
251 } |
460 } |