From 15906ca8744013ee114a9ca554b5e6835867ffc7 Mon Sep 17 00:00:00 2001 From: rubidium Date: Sat, 17 Dec 2011 12:15:19 +0000 Subject: [PATCH] (svn r23565) -Codechange: create some classes for writing language header and translation files --- src/strgen/strgen.cpp | 529 ++++++++++++++++++++++++++---------------- 1 file changed, 330 insertions(+), 199 deletions(-) diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp index 2c2e1ecf40..c87d63280f 100644 --- a/src/strgen/strgen.cpp +++ b/src/strgen/strgen.cpp @@ -18,6 +18,7 @@ #include "../table/control_codes.h" #include +#include #if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__) #include @@ -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"); - - 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"); - - 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; + if (this->fh == NULL) { + error("Could not open %s", this->filename); } } - fprintf(_output_file, "\nstatic const StringID STR_LAST_STRINGID = 0x%X;\n\n", next - 1); - - /* 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 the writing. */ + void Finalise() + { + fclose(this->fh); + this->fh = NULL; } - 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 - ); - - fprintf(_output_file, "#endif /* TABLE_STRINGS_H */\n"); - - fclose(_output_file); - _output_file = NULL; - - 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"); + /** 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); } - _output_filename = NULL; -} +}; + +/** 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; + + /** + * 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); + } +}; + +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; + + /** + * 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"); + } + + 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; + } + + 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"); + } + } +}; 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; + + /** + * 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; + + /** + * Finalise writing the file. + */ + virtual void Finalise() = 0; + + /** Especially destroy the subclasses. */ + virtual ~LanguageWriter() {} + + /** + * 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"); + } + + if (length >= 0xC0) { + buffer[offs++] = (length >> 8) | 0xC0; + } + buffer[offs++] = length & 0xFF; + this->Write((byte*)buffer, offs); } -} + /** + * Actually write the language. + */ + void WriteLang() + { + uint in_use[32]; + for (int i = 0; i != 32; i++) { + uint n = CountInUse(i); -static void WriteLangfile(const char *filename) -{ - uint in_use[32]; + in_use[i] = n; + _lang.offsets[i] = TO_LE16(n); - _output_filename = filename; - _output_file = fopen(filename, "wb"); - if (_output_file == NULL) error("can't open %s", filename); + 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++) { - uint n = CountInUse(i); + _lang.ident = TO_LE32(LanguagePackHeader::IDENT); + _lang.version = TO_LE32(_hash); + _lang.missing = TO_LE16(_lang.missing); + _lang.winlangid = TO_LE16(_lang.winlangid); - in_use[i] = n; - _lang.offsets[i] = TO_LE16(n); + this->WriteHeader(&_lang); - 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; + + /* For undefined strings, just set that it's an empty string */ + if (ls == NULL) { + this->WriteLength(0); + continue; + } + + _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 = " "; + while (*s != '\0') PutByte(*s++); + } + } + + /* 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; + } + + _translated = cmdp != ls->english; + + if (casep != NULL) { + const Case *c; + uint num; + + /* Need to output a case-switch. + * It has this format + * <0x9E> + * 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); + + this->WriteLength(_put_pos); + this->Write(_put_buf, _put_pos); + _put_pos = 0; + } } } +}; - _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); - - 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; - - /* For undefined strings, just set that it's an empty string */ - if (ls == NULL) { - WriteLength(_output_file, 0); - continue; - } - - _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 = " "; - while (*s != '\0') PutByte(*s++); - } - } - - /* 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; - } - - _translated = cmdp != ls->english; - - if (casep != NULL) { - const Case *c; - uint num; - - /* Need to output a case-switch. - * It has this format - * <0x9E> - * 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); - - WriteLength(_output_file, _put_pos); - fwrite(_put_buf, 1, _put_pos, _output_file); - _put_pos = 0; - } +/** 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) + { } - fputc(0, _output_file); - fclose(_output_file); + void WriteHeader(const LanguagePackHeader *header) + { + this->Write((const byte *)header, sizeof(*header)); + } - _output_file = NULL; - _output_filename = NULL; -} + 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"); + 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; + /* 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; + /* write strings.h */ + ottd_mkdir(dest_dir); + mkpath(pathbuf, lengthof(pathbuf), dest_dir, "strings.h"); - mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt"); + HeaderFileWriter writer(pathbuf); + writer.WriteHeader(); + writer.Finalise(); + } else if (mgo.numleft == 1) { + char *r; - /* 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; + mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt"); - /* 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]); + /* 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; - /* 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); + /* 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]); - /* 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); + /* 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;