changeset 18717:e9e4e17e3e19 draft

(svn r23565) -Codechange: create some classes for writing language header and translation files
author rubidium <rubidium@openttd.org>
date Sat, 17 Dec 2011 12:15:19 +0000
parents 30959e70d2f7
children 8aea5ed3cf0d
files src/strgen/strgen.cpp
diffstat 1 files changed, 314 insertions(+), 183 deletions(-) [+]
line wrap: on
line diff
--- 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 @@
 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 @@
 #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 @@
 	return true;
 }
 
-
-static void WriteStringsH(const char *filename)
-{
-	int next = -1;
-
-	_output_filename = "tmp.xxx";
-	_output_file = fopen(_output_filename, "w");
-	if (_output_file == NULL) error("can't open tmp.xxx");
+/** 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.
 
-	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");
+	/**
+	 * 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");
 
-	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
-	);
+	/** 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);
+	}
+};
+
+/** 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;
 
-	fprintf(_output_file, "#endif /* TABLE_STRINGS_H */\n");
+	/**
+	 * Finalise writing the file.
+	 */
+	virtual void Finalise() = 0;
+
+	/** Especially destroy the subclasses. */
+	virtual ~HeaderWriter() {};
 
-	fclose(_output_file);
-	_output_file = NULL;
+	/** 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;
 
-	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");
+	/**
+	 * 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;
 	}
-	_output_filename = NULL;
-}
+
+	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 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;
 
-static void WriteLangfile(const char *filename)
-{
-	uint in_use[32];
-
-	_output_filename = filename;
-	_output_file = fopen(filename, "wb");
-	if (_output_file == NULL) error("can't open %s", filename);
+	/** Especially destroy the subclasses. */
+	virtual ~LanguageWriter() {}
 
-	for (int i = 0; i != 32; i++) {
-		uint n = CountInUse(i);
+	/**
+	 * 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");
+		}
 
-		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++;
+		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);
 
-	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;
+			in_use[i] = n;
+			_lang.offsets[i] = TO_LE16(n);
 
-			/* For undefined strings, just set that it's an empty string */
-			if (ls == NULL) {
-				WriteLength(_output_file, 0);
-				continue;
+			for (uint j = 0; j != in_use[i]; j++) {
+				const LangString *ls = _strings[(i << 11) + j];
+				if (ls != NULL && ls->translated == NULL) _lang.missing++;
 			}
+		}
 
-			_cur_ident = ls->name;
-			_cur_line = ls->line;
+		_lang.ident = TO_LE32(LanguagePackHeader::IDENT);
+		_lang.version = TO_LE32(_hash);
+		_lang.missing = TO_LE16(_lang.missing);
+		_lang.winlangid = TO_LE16(_lang.winlangid);
+
+		this->WriteHeader(&_lang);
 
-			/* 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 (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;
 				}
-				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);
 
-			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;
+				/* Extract the strings and stuff from the english command string */
+				ExtractCommandString(&_cur_pcs, ls->english, false);
 
-			if (casep != NULL) {
-				const Case *c;
-				uint num;
+				if (ls->translated_case != NULL || ls->translated != NULL) {
+					casep = ls->translated_case;
+					cmdp = ls->translated;
+				} else {
+					casep = NULL;
+					cmdp = ls->english;
+				}
 
-				/* 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);
+				_translated = cmdp != ls->english;
+
+				if (casep != NULL) {
+					const Case *c;
+					uint num;
 
-				/* Write each case */
-				for (c = casep; c != NULL; c = c->next) {
-					uint pos;
+					/* 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);
+						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 @@
 
 	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;
+
+			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;
+			/* 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]);
+			/* 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);
+			/* 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);
+			/* 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;