summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrubidium <rubidium@openttd.org>2011-12-17 12:15:19 +0000
committerrubidium <rubidium@openttd.org>2011-12-17 12:15:19 +0000
commit15906ca8744013ee114a9ca554b5e6835867ffc7 (patch)
treef65f2d8f915b9c158ffcb4e602499da3f2a9bf1a
parent24b7be856bb6200f234f8ac2e6d3d365a4cf6aad (diff)
downloadopenttd-15906ca8744013ee114a9ca554b5e6835867ffc7.tar.xz
(svn r23565) -Codechange: create some classes for writing language header and translation files
-rw-r--r--src/strgen/strgen.cpp509
1 files changed, 320 insertions, 189 deletions
diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp
index 2c2e1ecf4..c87d63280 100644
--- a/src/strgen/strgen.cpp
+++ b/src/strgen/strgen.cpp
@@ -18,6 +18,7 @@
#include "../table/control_codes.h"
#include <stdarg.h>
+#include <exception>
#if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__)
#include <unistd.h>
@@ -48,8 +49,6 @@ struct Case {
static bool _translated; ///< Whether the current language is not the master language
static bool _translation; ///< Is the current file actually a translation or not
static const char *_file = "(unknown file)"; ///< The filename of the input, so we can refer to it in errors/warnings
-static FILE *_output_file = NULL; ///< The file we are currently writing output to
-static const char *_output_filename = NULL; ///< The filename of the output, so we can delete it if compilation fails
static int _cur_line; ///< The current line we're parsing in the input file
static int _errors, _warnings, _show_todo;
@@ -165,12 +164,7 @@ void NORETURN CDECL error(const char *s, ...)
#ifdef _MSC_VER
fprintf(stderr, LINE_NUM_FMT("warning"), _file, _cur_line, "language is not compiled");
#endif
- /* We were writing output to a file, remove it. */
- if (_output_file != NULL) {
- fclose(_output_file);
- unlink(_output_filename);
- }
- exit(1);
+ throw std::exception();
}
static void PutByte(byte c)
@@ -907,59 +901,134 @@ bool CompareFiles(const char *n1, const char *n2)
return true;
}
+/** Base class for writing data to disk. */
+struct FileWriter {
+ FILE *fh; ///< The file handle we're writing to.
+ const char *filename; ///< The file name we're writing to.
-static void WriteStringsH(const char *filename)
-{
- int next = -1;
+ /**
+ * Open a file to write to.
+ * @param filename The file to open.
+ */
+ FileWriter(const char *filename)
+ {
+ this->filename = strdup(filename);
+ this->fh = fopen(this->filename, "wb");
- _output_filename = "tmp.xxx";
- _output_file = fopen(_output_filename, "w");
- if (_output_file == NULL) error("can't open tmp.xxx");
+ if (this->fh == NULL) {
+ error("Could not open %s", this->filename);
+ }
+ }
- fprintf(_output_file, "/* This file is automatically generated. Do not modify */\n\n");
- fprintf(_output_file, "#ifndef TABLE_STRINGS_H\n");
- fprintf(_output_file, "#define TABLE_STRINGS_H\n");
+ /** Finalise the writing. */
+ void Finalise()
+ {
+ fclose(this->fh);
+ this->fh = NULL;
+ }
- for (int i = 0; i != lengthof(_strings); i++) {
- if (_strings[i] != NULL) {
- if (next != i) fprintf(_output_file, "\n");
- fprintf(_output_file, "static const StringID %s = 0x%X;\n", _strings[i]->name, i);
- next = i + 1;
+ /** Make sure the file is closed. */
+ virtual ~FileWriter()
+ {
+ printf("close %s %p\n", this->filename, this->fh);
+ /* If we weren't closed an exception was thrown, so remove the termporary file. */
+ if (fh != NULL) {
+ fclose(this->fh);
+ unlink(this->filename);
}
+ free(this->filename);
}
+};
- fprintf(_output_file, "\nstatic const StringID STR_LAST_STRINGID = 0x%X;\n\n", next - 1);
+/** Base class for writing the header. */
+struct HeaderWriter {
+ /**
+ * Write the string ID.
+ * @param name The name of the string.
+ * @param stringid The ID of the string.
+ */
+ virtual void WriteStringID(const char *name, int stringid) = 0;
- /* Find the plural form with the most amount of cases. */
- int max_plural_forms = 0;
- for (uint i = 0; i < lengthof(_plural_forms); i++) {
- max_plural_forms = max(max_plural_forms, _plural_forms[i].plural_count);
+ /**
+ * Finalise writing the file.
+ */
+ virtual void Finalise() = 0;
+
+ /** Especially destroy the subclasses. */
+ virtual ~HeaderWriter() {};
+
+ /** Write the header information. */
+ void WriteHeader()
+ {
+ int last = 0;
+ for (int i = 0; i != lengthof(_strings); i++) {
+ if (_strings[i] != NULL) {
+ this->WriteStringID(_strings[i]->name, i);
+ last = i;
+ }
+ }
+
+ this->WriteStringID("STR_LAST_STRINGID", last);
}
+};
- fprintf(_output_file,
- "static const uint LANGUAGE_PACK_VERSION = 0x%X;\n"
- "static const uint LANGUAGE_MAX_PLURAL = %d;\n"
- "static const uint LANGUAGE_MAX_PLURAL_FORMS = %d;\n\n",
- (uint)_hash, (uint)lengthof(_plural_forms), max_plural_forms
- );
+struct HeaderFileWriter : HeaderWriter, FileWriter {
+ /** The real file name we eventually want to write to. */
+ const char *real_filename;
+ /** The previous string ID that was printed. */
+ int prev;
- fprintf(_output_file, "#endif /* TABLE_STRINGS_H */\n");
+ /**
+ * Open a file to write to.
+ * @param filename The file to open.
+ */
+ HeaderFileWriter(const char *filename) : FileWriter("tmp.xxx"),
+ real_filename(strdup(filename)), prev(0)
+ {
+ fprintf(this->fh, "/* This file is automatically generated. Do not modify */\n\n");
+ fprintf(this->fh, "#ifndef TABLE_STRINGS_H\n");
+ fprintf(this->fh, "#define TABLE_STRINGS_H\n");
+ }
- fclose(_output_file);
- _output_file = NULL;
+ void WriteStringID(const char *name, int stringid)
+ {
+ if (prev + 1 != stringid) fprintf(this->fh, "\n");
+ fprintf(this->fh, "static const StringID %s = 0x%X;\n", name, stringid);
+ prev = stringid;
+ }
- if (CompareFiles(_output_filename, filename)) {
- /* files are equal. tmp.xxx is not needed */
- unlink(_output_filename);
- } else {
- /* else rename tmp.xxx into filename */
-#if defined(WIN32) || defined(WIN64)
- unlink(filename);
-#endif
- if (rename(_output_filename, filename) == -1) error("rename() failed");
+ void Finalise()
+ {
+ /* Find the plural form with the most amount of cases. */
+ int max_plural_forms = 0;
+ for (uint i = 0; i < lengthof(_plural_forms); i++) {
+ max_plural_forms = max(max_plural_forms, _plural_forms[i].plural_count);
+ }
+
+ fprintf(this->fh,
+ "\n"
+ "static const uint LANGUAGE_PACK_VERSION = 0x%X;\n"
+ "static const uint LANGUAGE_MAX_PLURAL = %d;\n"
+ "static const uint LANGUAGE_MAX_PLURAL_FORMS = %d;\n\n",
+ (uint)_hash, (uint)lengthof(_plural_forms), max_plural_forms
+ );
+
+ fprintf(this->fh, "#endif /* TABLE_STRINGS_H */\n");
+
+ this->FileWriter::Finalise();
+
+ if (CompareFiles(this->filename, this->real_filename)) {
+ /* files are equal. tmp.xxx is not needed */
+ unlink(this->filename);
+ } else {
+ /* else rename tmp.xxx into filename */
+ #if defined(WIN32) || defined(WIN64)
+ unlink(filename);
+ #endif
+ if (rename(this->filename, this->real_filename) == -1) error("rename() failed");
+ }
}
- _output_filename = NULL;
-}
+};
static int TranslateArgumentIdx(int argidx, int offset)
{
@@ -1034,130 +1103,182 @@ static void PutCommandString(const char *str)
}
}
-static void WriteLength(FILE *f, uint length)
-{
- if (length < 0xC0) {
- fputc(length, f);
- } else if (length < 0x4000) {
- fputc((length >> 8) | 0xC0, f);
- fputc(length & 0xFF, f);
- } else {
- error("string too long");
- }
-}
-
+/** Base class for all language writers. */
+struct LanguageWriter {
+ /**
+ * Write the header metadata. The multi-byte integers are already converted to
+ * the little endian format.
+ * @param header The header to write.
+ */
+ virtual void WriteHeader(const LanguagePackHeader *header) = 0;
-static void WriteLangfile(const char *filename)
-{
- uint in_use[32];
+ /**
+ * Write a number of bytes.
+ * @param buffer The buffer to write.
+ * @param length The amount of byte to write.
+ */
+ virtual void Write(const byte *buffer, size_t length) = 0;
- _output_filename = filename;
- _output_file = fopen(filename, "wb");
- if (_output_file == NULL) error("can't open %s", filename);
+ /**
+ * Finalise writing the file.
+ */
+ virtual void Finalise() = 0;
- for (int i = 0; i != 32; i++) {
- uint n = CountInUse(i);
+ /** Especially destroy the subclasses. */
+ virtual ~LanguageWriter() {}
- in_use[i] = n;
- _lang.offsets[i] = TO_LE16(n);
+ /**
+ * Write the length as a simple gamma.
+ * @param length The number to write.
+ */
+ void WriteLength(uint length)
+ {
+ char buffer[2];
+ int offs = 0;
+ if (length >= 0x4000) {
+ error("string too long");
+ }
- for (uint j = 0; j != in_use[i]; j++) {
- const LangString *ls = _strings[(i << 11) + j];
- if (ls != NULL && ls->translated == NULL) _lang.missing++;
+ if (length >= 0xC0) {
+ buffer[offs++] = (length >> 8) | 0xC0;
}
+ buffer[offs++] = length & 0xFF;
+ this->Write((byte*)buffer, offs);
}
- _lang.ident = TO_LE32(LanguagePackHeader::IDENT);
- _lang.version = TO_LE32(_hash);
- _lang.missing = TO_LE16(_lang.missing);
- _lang.winlangid = TO_LE16(_lang.winlangid);
-
- fwrite(&_lang, sizeof(_lang), 1, _output_file);
+ /**
+ * Actually write the language.
+ */
+ void WriteLang()
+ {
+ uint in_use[32];
+ for (int i = 0; i != 32; i++) {
+ uint n = CountInUse(i);
+
+ in_use[i] = n;
+ _lang.offsets[i] = TO_LE16(n);
+
+ for (uint j = 0; j != in_use[i]; j++) {
+ const LangString *ls = _strings[(i << 11) + j];
+ if (ls != NULL && ls->translated == NULL) _lang.missing++;
+ }
+ }
- for (int i = 0; i != 32; i++) {
- for (uint j = 0; j != in_use[i]; j++) {
- const LangString *ls = _strings[(i << 11) + j];
- const Case *casep;
- const char *cmdp;
+ _lang.ident = TO_LE32(LanguagePackHeader::IDENT);
+ _lang.version = TO_LE32(_hash);
+ _lang.missing = TO_LE16(_lang.missing);
+ _lang.winlangid = TO_LE16(_lang.winlangid);
- /* For undefined strings, just set that it's an empty string */
- if (ls == NULL) {
- WriteLength(_output_file, 0);
- continue;
- }
+ this->WriteHeader(&_lang);
- _cur_ident = ls->name;
- _cur_line = ls->line;
+ for (int i = 0; i != 32; i++) {
+ for (uint j = 0; j != in_use[i]; j++) {
+ const LangString *ls = _strings[(i << 11) + j];
+ const Case *casep;
+ const char *cmdp;
- /* Produce a message if a string doesn't have a translation. */
- if (_show_todo > 0 && ls->translated == NULL) {
- if ((_show_todo & 2) != 0) {
- strgen_warning("'%s' is untranslated", ls->name);
+ /* For undefined strings, just set that it's an empty string */
+ if (ls == NULL) {
+ this->WriteLength(0);
+ continue;
}
- if ((_show_todo & 1) != 0) {
- const char *s = "<TODO> ";
- while (*s != '\0') PutByte(*s++);
+
+ _cur_ident = ls->name;
+ _cur_line = ls->line;
+
+ /* Produce a message if a string doesn't have a translation. */
+ if (_show_todo > 0 && ls->translated == NULL) {
+ if ((_show_todo & 2) != 0) {
+ strgen_warning("'%s' is untranslated", ls->name);
+ }
+ if ((_show_todo & 1) != 0) {
+ const char *s = "<TODO> ";
+ while (*s != '\0') PutByte(*s++);
+ }
}
- }
- /* Extract the strings and stuff from the english command string */
- ExtractCommandString(&_cur_pcs, ls->english, false);
+ /* Extract the strings and stuff from the english command string */
+ ExtractCommandString(&_cur_pcs, ls->english, false);
- if (ls->translated_case != NULL || ls->translated != NULL) {
- casep = ls->translated_case;
- cmdp = ls->translated;
- } else {
- casep = NULL;
- cmdp = ls->english;
- }
+ if (ls->translated_case != NULL || ls->translated != NULL) {
+ casep = ls->translated_case;
+ cmdp = ls->translated;
+ } else {
+ casep = NULL;
+ cmdp = ls->english;
+ }
- _translated = cmdp != ls->english;
-
- if (casep != NULL) {
- const Case *c;
- uint num;
-
- /* Need to output a case-switch.
- * It has this format
- * <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
- * Each LEN is printed using 2 bytes in big endian order. */
- PutUtf8(SCC_SWITCH_CASE);
- /* Count the number of cases */
- for (num = 0, c = casep; c; c = c->next) num++;
- PutByte(num);
-
- /* Write each case */
- for (c = casep; c != NULL; c = c->next) {
- uint pos;
-
- PutByte(c->caseidx);
- /* Make some space for the 16-bit length */
- pos = _put_pos;
- PutByte(0);
- PutByte(0);
- /* Write string */
- PutCommandString(c->string);
- PutByte(0); // terminate with a zero
- /* Fill in the length */
- _put_buf[pos + 0] = GB(_put_pos - (pos + 2), 8, 8);
- _put_buf[pos + 1] = GB(_put_pos - (pos + 2), 0, 8);
+ _translated = cmdp != ls->english;
+
+ if (casep != NULL) {
+ const Case *c;
+ uint num;
+
+ /* Need to output a case-switch.
+ * It has this format
+ * <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
+ * Each LEN is printed using 2 bytes in big endian order. */
+ PutUtf8(SCC_SWITCH_CASE);
+ /* Count the number of cases */
+ for (num = 0, c = casep; c; c = c->next) num++;
+ PutByte(num);
+
+ /* Write each case */
+ for (c = casep; c != NULL; c = c->next) {
+ uint pos;
+
+ PutByte(c->caseidx);
+ /* Make some space for the 16-bit length */
+ pos = _put_pos;
+ PutByte(0);
+ PutByte(0);
+ /* Write string */
+ PutCommandString(c->string);
+ PutByte(0); // terminate with a zero
+ /* Fill in the length */
+ _put_buf[pos + 0] = GB(_put_pos - (pos + 2), 8, 8);
+ _put_buf[pos + 1] = GB(_put_pos - (pos + 2), 0, 8);
+ }
}
- }
- if (cmdp != NULL) PutCommandString(cmdp);
+ if (cmdp != NULL) PutCommandString(cmdp);
- WriteLength(_output_file, _put_pos);
- fwrite(_put_buf, 1, _put_pos, _output_file);
- _put_pos = 0;
+ this->WriteLength(_put_pos);
+ this->Write(_put_buf, _put_pos);
+ _put_pos = 0;
+ }
}
}
+};
- fputc(0, _output_file);
- fclose(_output_file);
+/** Class for writing a language to disk. */
+struct LanguageFileWriter : LanguageWriter, FileWriter {
+ /**
+ * Open a file to write to.
+ * @param filename The file to open.
+ */
+ LanguageFileWriter(const char *filename) : FileWriter(filename)
+ {
+ }
- _output_file = NULL;
- _output_filename = NULL;
-}
+ void WriteHeader(const LanguagePackHeader *header)
+ {
+ this->Write((const byte *)header, sizeof(*header));
+ }
+
+ void Finalise()
+ {
+ fputc(0, this->fh);
+ this->FileWriter::Finalise();
+ }
+
+ void Write(const byte *buffer, size_t length)
+ {
+ if (fwrite(buffer, sizeof(*buffer), length, this->fh) != length) {
+ error("Could not write to %s", this->filename);
+ }
+ }
+};
/** Multi-OS mkdirectory function */
static inline void ottd_mkdir(const char *directory)
@@ -1314,49 +1435,59 @@ int CDECL main(int argc, char *argv[])
if (dest_dir == NULL) dest_dir = src_dir; // if dest_dir is not specified, it equals src_dir
- /* strgen has two modes of operation. If no (free) arguments are passed
- * strgen generates strings.h to the destination directory. If it is supplied
- * with a (free) parameter the program will translate that language to destination
- * directory. As input english.txt is parsed from the source directory */
- if (mgo.numleft == 0) {
- mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
-
- /* parse master file */
- ParseFile(pathbuf, true);
- MakeHashOfStrings();
- if (_errors != 0) return 1;
-
- /* write strings.h */
- ottd_mkdir(dest_dir);
- mkpath(pathbuf, lengthof(pathbuf), dest_dir, "strings.h");
- WriteStringsH(pathbuf);
- } else if (mgo.numleft == 1) {
- char *r;
-
- mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
-
- /* parse master file and check if target file is correct */
- ParseFile(pathbuf, true);
- MakeHashOfStrings();
- ParseFile(replace_pathsep(mgo.argv[0]), false); // target file
- if (_errors != 0) return 1;
-
- /* get the targetfile, strip any directories and append to destination path */
- r = strrchr(mgo.argv[0], PATHSEPCHAR);
- mkpath(pathbuf, lengthof(pathbuf), dest_dir, (r != NULL) ? &r[1] : mgo.argv[0]);
-
- /* rename the .txt (input-extension) to .lng */
- r = strrchr(pathbuf, '.');
- if (r == NULL || strcmp(r, ".txt") != 0) r = strchr(pathbuf, '\0');
- ttd_strlcpy(r, ".lng", (size_t)(r - pathbuf));
- WriteLangfile(pathbuf);
-
- /* if showing warnings, print a summary of the language */
- if ((_show_todo & 2) != 0) {
- fprintf(stdout, "%d warnings and %d errors for %s\n", _warnings, _errors, pathbuf);
+ try {
+ /* strgen has two modes of operation. If no (free) arguments are passed
+ * strgen generates strings.h to the destination directory. If it is supplied
+ * with a (free) parameter the program will translate that language to destination
+ * directory. As input english.txt is parsed from the source directory */
+ if (mgo.numleft == 0) {
+ mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
+
+ /* parse master file */
+ ParseFile(pathbuf, true);
+ MakeHashOfStrings();
+ if (_errors != 0) return 1;
+
+ /* write strings.h */
+ ottd_mkdir(dest_dir);
+ mkpath(pathbuf, lengthof(pathbuf), dest_dir, "strings.h");
+
+ HeaderFileWriter writer(pathbuf);
+ writer.WriteHeader();
+ writer.Finalise();
+ } else if (mgo.numleft == 1) {
+ char *r;
+
+ mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
+
+ /* parse master file and check if target file is correct */
+ ParseFile(pathbuf, true);
+ MakeHashOfStrings();
+ ParseFile(replace_pathsep(mgo.argv[0]), false); // target file
+ if (_errors != 0) return 1;
+
+ /* get the targetfile, strip any directories and append to destination path */
+ r = strrchr(mgo.argv[0], PATHSEPCHAR);
+ mkpath(pathbuf, lengthof(pathbuf), dest_dir, (r != NULL) ? &r[1] : mgo.argv[0]);
+
+ /* rename the .txt (input-extension) to .lng */
+ r = strrchr(pathbuf, '.');
+ if (r == NULL || strcmp(r, ".txt") != 0) r = strchr(pathbuf, '\0');
+ ttd_strlcpy(r, ".lng", (size_t)(r - pathbuf));
+
+ LanguageFileWriter writer(pathbuf);
+ writer.WriteLang();
+ writer.Finalise();
+
+ /* if showing warnings, print a summary of the language */
+ if ((_show_todo & 2) != 0) {
+ fprintf(stdout, "%d warnings and %d errors for %s\n", _warnings, _errors, pathbuf);
+ }
+ } else {
+ fprintf(stderr, "Invalid arguments\n");
}
- } else {
- fprintf(stderr, "Invalid arguments\n");
+ } catch (...) {
+ return 2;
}
return 0;