strgen.cpp

Go to the documentation of this file.
00001 /* $Id: strgen.cpp 18727 2010-01-04 21:58:47Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "../stdafx.h"
00013 #include "../core/alloc_func.hpp"
00014 #include "../core/endian_func.hpp"
00015 #include "../string_func.h"
00016 #include "../strings_type.h"
00017 #include "strgen.h"
00018 #include "../table/control_codes.h"
00019 
00020 #include <stdarg.h>
00021 
00022 #if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__)
00023 #include <unistd.h>
00024 #include <sys/stat.h>
00025 #endif
00026 
00027 #if defined WIN32 || defined __WATCOMC__
00028 #include <direct.h>
00029 #endif /* WIN32 || __WATCOMC__ */
00030 
00031 #ifdef __MORPHOS__
00032 #ifdef stderr
00033 #undef stderr
00034 #endif
00035 #define stderr stdout
00036 #endif /* __MORPHOS__ */
00037 
00038 #include "../table/strgen_tables.h"
00039 
00040 /* Compiles a list of strings into a compiled string list */
00041 
00042 struct Case {
00043   int caseidx;
00044   char *string;
00045   Case *next;
00046 };
00047 
00048 static bool _masterlang;
00049 static bool _translated;
00050 static bool _translation; 
00051 static const char *_file = "(unknown file)";
00052 static int _cur_line;
00053 static int _errors, _warnings, _show_todo;
00054 
00055 struct LangString {
00056   char *name;            // Name of the string
00057   char *english;         // English text
00058   char *translated;      // Translated text
00059   uint16 hash_next;      // next hash entry
00060   uint16 index;
00061   int line;              // line of string in source-file
00062   Case *english_case;    // cases for english
00063   Case *translated_case; // cases for foreign
00064 };
00065 
00066 static LangString *_strings[65536];
00067 
00068 
00069 #define HASH_SIZE 32767
00070 static uint16 _hash_head[HASH_SIZE];
00071 
00072 static byte _put_buf[4096];
00073 static int _put_pos;
00074 static int _next_string_id;
00075 
00076 static uint32 _hash;
00077 static char _lang_name[32], _lang_ownname[32], _lang_isocode[16];
00078 static char _lang_digit_group_separator[8];
00079 static char _lang_digit_group_separator_currency[8];
00080 static char _lang_digit_decimal_separator[8];
00081 static byte _lang_pluralform;
00082 static byte _lang_textdir;
00083 static uint16 _lang_winlangid;
00084 static uint8 _lang_newgrflangid;
00085 #define MAX_NUM_GENDER 8
00086 static char _genders[MAX_NUM_GENDER][16];
00087 static uint _numgenders;
00088 
00089 /* contains the name of all cases. */
00090 #define MAX_NUM_CASES 50
00091 static char _cases[MAX_NUM_CASES][16];
00092 static uint _numcases;
00093 
00094 static const char *_cur_ident;
00095 
00096 struct CmdPair {
00097   const CmdStruct *a;
00098   const char *v;
00099 };
00100 
00101 struct ParsedCommandStruct {
00102   uint np;
00103   CmdPair pairs[32];
00104   const CmdStruct *cmd[32]; // ordered by param #
00105 };
00106 
00107 /* Used when generating some advanced commands. */
00108 static ParsedCommandStruct _cur_pcs;
00109 static int _cur_argidx;
00110 
00111 static uint HashStr(const char *s)
00112 {
00113   uint hash = 0;
00114   for (; *s != '\0'; s++) hash = ROL(hash, 3) ^ *s;
00115   return hash % HASH_SIZE;
00116 }
00117 
00118 static void HashAdd(const char *s, LangString *ls)
00119 {
00120   uint hash = HashStr(s);
00121   ls->hash_next = _hash_head[hash];
00122   _hash_head[hash] = ls->index + 1;
00123 }
00124 
00125 static LangString *HashFind(const char *s)
00126 {
00127   int idx = _hash_head[HashStr(s)];
00128 
00129   while (--idx >= 0) {
00130     LangString *ls = _strings[idx];
00131 
00132     if (strcmp(ls->name, s) == 0) return ls;
00133     idx = ls->hash_next;
00134   }
00135   return NULL;
00136 }
00137 
00138 #ifdef _MSC_VER
00139 # define LINE_NUM_FMT "(%d)"
00140 #else
00141 # define LINE_NUM_FMT ":%d"
00142 #endif
00143 
00144 static void CDECL strgen_warning(const char *s, ...) WARN_FORMAT(1, 2);
00145 
00146 static void CDECL strgen_warning(const char *s, ...)
00147 {
00148   char buf[1024];
00149   va_list va;
00150   va_start(va, s);
00151   vsnprintf(buf, lengthof(buf), s, va);
00152   va_end(va);
00153   fprintf(stderr, "%s" LINE_NUM_FMT ": warning: %s\n", _file, _cur_line, buf);
00154   _warnings++;
00155 }
00156 
00157 static void CDECL strgen_error(const char *s, ...) WARN_FORMAT(1, 2);
00158 
00159 static void CDECL strgen_error(const char *s, ...)
00160 {
00161   char buf[1024];
00162   va_list va;
00163   va_start(va, s);
00164   vsnprintf(buf, lengthof(buf), s, va);
00165   va_end(va);
00166   fprintf(stderr, "%s" LINE_NUM_FMT ": error: %s\n", _file, _cur_line, buf);
00167   _errors++;
00168 }
00169 
00170 void NORETURN CDECL error(const char *s, ...)
00171 {
00172   char buf[1024];
00173   va_list va;
00174   va_start(va, s);
00175   vsnprintf(buf, lengthof(buf), s, va);
00176   va_end(va);
00177   fprintf(stderr, "%s" LINE_NUM_FMT ": FATAL: %s\n", _file, _cur_line, buf);
00178   exit(1);
00179 }
00180 
00181 static void PutByte(byte c)
00182 {
00183   if (_put_pos == lengthof(_put_buf)) error("Put buffer too small");
00184   _put_buf[_put_pos++] = c;
00185 }
00186 
00187 
00188 static void PutUtf8(uint32 value)
00189 {
00190   if (value < 0x80) {
00191     PutByte(value);
00192   } else if (value < 0x800) {
00193     PutByte(0xC0 + GB(value,  6, 5));
00194     PutByte(0x80 + GB(value,  0, 6));
00195   } else if (value < 0x10000) {
00196     PutByte(0xE0 + GB(value, 12, 4));
00197     PutByte(0x80 + GB(value,  6, 6));
00198     PutByte(0x80 + GB(value,  0, 6));
00199   } else if (value < 0x110000) {
00200     PutByte(0xF0 + GB(value, 18, 3));
00201     PutByte(0x80 + GB(value, 12, 6));
00202     PutByte(0x80 + GB(value,  6, 6));
00203     PutByte(0x80 + GB(value,  0, 6));
00204   } else {
00205     strgen_warning("Invalid unicode value U+0x%X", value);
00206   }
00207 }
00208 
00209 
00210 size_t Utf8Validate(const char *s)
00211 {
00212   uint32 c;
00213 
00214   if (!HasBit(s[0], 7)) {
00215     /* 1 byte */
00216     return 1;
00217   } else if (GB(s[0], 5, 3) == 6 && IsUtf8Part(s[1])) {
00218     /* 2 bytes */
00219     c = GB(s[0], 0, 5) << 6 | GB(s[1], 0, 6);
00220     if (c >= 0x80) return 2;
00221   } else if (GB(s[0], 4, 4) == 14 && IsUtf8Part(s[1]) && IsUtf8Part(s[2])) {
00222     /* 3 bytes */
00223     c = GB(s[0], 0, 4) << 12 | GB(s[1], 0, 6) << 6 | GB(s[2], 0, 6);
00224     if (c >= 0x800) return 3;
00225   } else if (GB(s[0], 3, 5) == 30 && IsUtf8Part(s[1]) && IsUtf8Part(s[2]) && IsUtf8Part(s[3])) {
00226     /* 4 bytes */
00227     c = GB(s[0], 0, 3) << 18 | GB(s[1], 0, 6) << 12 | GB(s[2], 0, 6) << 6 | GB(s[3], 0, 6);
00228     if (c >= 0x10000 && c <= 0x10FFFF) return 4;
00229   }
00230 
00231   return 0;
00232 }
00233 
00234 
00235 static void EmitSingleChar(char *buf, int value)
00236 {
00237   if (*buf != '\0') strgen_warning("Ignoring trailing letters in command");
00238   PutUtf8(value);
00239 }
00240 
00241 
00242 /* The plural specifier looks like
00243  * {NUM} {PLURAL -1 passenger passengers} then it picks either passenger/passengers depending on the count in NUM */
00244 
00245 /* This is encoded like
00246  *  CommandByte <ARG#> <NUM> {Length of each string} {each string} */
00247 
00248 bool ParseRelNum(char **buf, int *value, int *offset)
00249 {
00250   const char *s = *buf;
00251   char *end;
00252   bool rel = false;
00253 
00254   while (*s == ' ' || *s == '\t') s++;
00255   if (*s == '+') {
00256     rel = true;
00257     s++;
00258   }
00259   int v = strtol(s, &end, 0);
00260   if (end == s) return false;
00261   if (rel || v < 0) {
00262     *value += v;
00263   } else {
00264     *value = v;
00265   }
00266   if (offset != NULL && *end == ':') {
00267     /* Take the Nth within */
00268     s = end + 1;
00269     *offset = strtol(s, &end, 0);
00270     if (end == s) return false;
00271   }
00272   *buf = end;
00273   return true;
00274 }
00275 
00276 /* Parse out the next word, or NULL */
00277 char *ParseWord(char **buf)
00278 {
00279   char *s = *buf, *r;
00280 
00281   while (*s == ' ' || *s == '\t') s++;
00282   if (*s == '\0') return NULL;
00283 
00284   if (*s == '"') {
00285     r = ++s;
00286     /* parse until next " or NUL */
00287     for (;;) {
00288       if (*s == '\0') break;
00289       if (*s == '"') {
00290         *s++ = '\0';
00291         break;
00292       }
00293       s++;
00294     }
00295   } else {
00296     /* proceed until whitespace or NUL */
00297     r = s;
00298     for (;;) {
00299       if (*s == '\0') break;
00300       if (*s == ' ' || *s == '\t') {
00301         *s++ = '\0';
00302         break;
00303       }
00304       s++;
00305     }
00306   }
00307   *buf = s;
00308   return r;
00309 }
00310 
00311 /* Forward declaration */
00312 static int TranslateArgumentIdx(int arg, int offset = 0);
00313 
00314 static void EmitWordList(const char * const *words, uint nw)
00315 {
00316   PutByte(nw);
00317   for (uint i = 0; i < nw; i++) PutByte(strlen(words[i]) + 1);
00318   for (uint i = 0; i < nw; i++) {
00319     for (uint j = 0; words[i][j] != '\0'; j++) PutByte(words[i][j]);
00320     PutByte(0);
00321   }
00322 }
00323 
00324 static void EmitPlural(char *buf, int value)
00325 {
00326   int argidx = _cur_argidx;
00327   int offset = 0;
00328   const char *words[5];
00329   int nw = 0;
00330 
00331   /* Parse out the number, if one exists. Otherwise default to prev arg. */
00332   if (!ParseRelNum(&buf, &argidx, &offset)) argidx--;
00333 
00334   /* Parse each string */
00335   for (nw = 0; nw < 5; nw++) {
00336     words[nw] = ParseWord(&buf);
00337     if (words[nw] == NULL) break;
00338   }
00339 
00340   if (nw == 0) {
00341     error("%s: No plural words", _cur_ident);
00342   }
00343 
00344   if (_plural_forms[_lang_pluralform].plural_count != nw) {
00345     if (_translated) {
00346       error("%s: Invalid number of plural forms. Expecting %d, found %d.", _cur_ident,
00347         _plural_forms[_lang_pluralform].plural_count, nw);
00348     } else {
00349       if ((_show_todo & 2) != 0) strgen_warning("'%s' is untranslated. Tweaking english string to allow compilation for plural forms", _cur_ident);
00350       if (nw > _plural_forms[_lang_pluralform].plural_count) {
00351         nw = _plural_forms[_lang_pluralform].plural_count;
00352       } else {
00353         for (; nw < _plural_forms[_lang_pluralform].plural_count; nw++) {
00354           words[nw] = words[nw - 1];
00355         }
00356       }
00357     }
00358   }
00359 
00360   PutUtf8(SCC_PLURAL_LIST);
00361   PutByte(TranslateArgumentIdx(argidx, offset));
00362   EmitWordList(words, nw);
00363 }
00364 
00365 
00366 static void EmitGender(char *buf, int value)
00367 {
00368   int argidx = _cur_argidx;
00369   int offset = 0;
00370   uint nw;
00371 
00372   if (buf[0] == '=') {
00373     buf++;
00374 
00375     /* This is a {G=DER} command */
00376     for (nw = 0; ; nw++) {
00377       if (nw >= MAX_NUM_GENDER) error("G argument '%s' invalid", buf);
00378       if (strcmp(buf, _genders[nw]) == 0) break;
00379     }
00380     /* now nw contains the gender index */
00381     PutUtf8(SCC_GENDER_INDEX);
00382     PutByte(nw);
00383   } else {
00384     const char *words[MAX_NUM_GENDER];
00385 
00386     /* This is a {G 0 foo bar two} command.
00387      * If no relative number exists, default to +0 */
00388     if (!ParseRelNum(&buf, &argidx, &offset)) {}
00389 
00390     for (nw = 0; nw < MAX_NUM_GENDER; nw++) {
00391       words[nw] = ParseWord(&buf);
00392       if (words[nw] == NULL) break;
00393     }
00394     if (nw != _numgenders) error("Bad # of arguments for gender command");
00395     PutUtf8(SCC_GENDER_LIST);
00396     PutByte(TranslateArgumentIdx(argidx, offset));
00397     EmitWordList(words, nw);
00398   }
00399 }
00400 
00401 static const CmdStruct *FindCmd(const char *s, int len)
00402 {
00403   for (const CmdStruct *cs = _cmd_structs; cs != endof(_cmd_structs); cs++) {
00404     if (strncmp(cs->cmd, s, len) == 0 && cs->cmd[len] == '\0') return cs;
00405   }
00406   return NULL;
00407 }
00408 
00409 static uint ResolveCaseName(const char *str, uint len)
00410 {
00411   for (uint i = 0; i < MAX_NUM_CASES; i++) {
00412     if (memcmp(_cases[i], str, len) == 0 && _cases[i][len] == 0) return i + 1;
00413   }
00414   error("Invalid case-name '%s'", str);
00415 }
00416 
00417 
00418 /* returns NULL on eof
00419  * else returns command struct */
00420 static const CmdStruct *ParseCommandString(const char **str, char *param, int *argno, int *casei)
00421 {
00422   const char *s = *str, *start;
00423   char c;
00424 
00425   *argno = -1;
00426   *casei = -1;
00427 
00428   /* Scan to the next command, exit if there's no next command. */
00429   for (; *s != '{'; s++) {
00430     if (*s == '\0') return NULL;
00431   }
00432   s++; // Skip past the {
00433 
00434   if (*s >= '0' && *s <= '9') {
00435     char *end;
00436 
00437     *argno = strtoul(s, &end, 0);
00438     if (*end != ':') error("missing arg #");
00439     s = end + 1;
00440   }
00441 
00442   /* parse command name */
00443   start = s;
00444   do {
00445     c = *s++;
00446   } while (c != '}' && c != ' ' && c != '=' && c != '.' && c != 0);
00447 
00448   const CmdStruct *cmd = FindCmd(start, s - start - 1);
00449   if (cmd == NULL) {
00450     strgen_error("Undefined command '%.*s'", (int)(s - start - 1), start);
00451     return NULL;
00452   }
00453 
00454   if (c == '.') {
00455     const char *casep = s;
00456 
00457     if (!(cmd->flags & C_CASE)) {
00458       error("Command '%s' can't have a case", cmd->cmd);
00459     }
00460 
00461     do {
00462       c = *s++;
00463     } while (c != '}' && c != ' ' && c != '\0');
00464     *casei = ResolveCaseName(casep, s - casep - 1);
00465   }
00466 
00467   if (c == '\0') {
00468     strgen_error("Missing } from command '%s'", start);
00469     return NULL;
00470   }
00471 
00472 
00473   if (c != '}') {
00474     if (c == '=') s--;
00475     /* copy params */
00476     start = s;
00477     for (;;) {
00478       c = *s++;
00479       if (c == '}') break;
00480       if (c == '\0') {
00481         strgen_error("Missing } from command '%s'", start);
00482         return NULL;
00483       }
00484       if (s - start == 250) error("param command too long");
00485       *param++ = c;
00486     }
00487   }
00488   *param = '\0';
00489 
00490   *str = s;
00491 
00492   return cmd;
00493 }
00494 
00495 
00496 static void HandlePragma(char *str)
00497 {
00498   if (!memcmp(str, "id ", 3)) {
00499     _next_string_id = strtoul(str + 3, NULL, 0);
00500   } else if (!memcmp(str, "name ", 5)) {
00501     strecpy(_lang_name, str + 5, lastof(_lang_name));
00502   } else if (!memcmp(str, "ownname ", 8)) {
00503     strecpy(_lang_ownname, str + 8, lastof(_lang_ownname));
00504   } else if (!memcmp(str, "isocode ", 8)) {
00505     strecpy(_lang_isocode, str + 8, lastof(_lang_isocode));
00506   } else if (!memcmp(str, "plural ", 7)) {
00507     _lang_pluralform = atoi(str + 7);
00508     if (_lang_pluralform >= lengthof(_plural_forms))
00509       error("Invalid pluralform %d", _lang_pluralform);
00510   } else if (!memcmp(str, "textdir ", 8)) {
00511     if (!memcmp(str + 8, "ltr", 3)) {
00512       _lang_textdir = TD_LTR;
00513     } else if (!memcmp(str + 8, "rtl", 3)) {
00514       _lang_textdir = TD_RTL;
00515     } else {
00516       error("Invalid textdir %s", str + 8);
00517     }
00518   } else if (!memcmp(str, "digitsep ", 9)) {
00519     str += 9;
00520     strecpy(_lang_digit_group_separator, strcmp(str, "{NBSP}") == 0 ? "\xC2\xA0" : str, lastof(_lang_digit_group_separator));
00521   } else if (!memcmp(str, "digitsepcur ", 12)) {
00522     str += 12;
00523     strecpy(_lang_digit_group_separator_currency, strcmp(str, "{NBSP}") == 0 ? "\xC2\xA0" : str, lastof(_lang_digit_group_separator_currency));
00524   } else if (!memcmp(str, "decimalsep ", 11)) {
00525     str += 11;
00526     strecpy(_lang_digit_decimal_separator, strcmp(str, "{NBSP}") == 0 ? "\xC2\xA0" : str, lastof(_lang_digit_decimal_separator));
00527   } else if (!memcmp(str, "winlangid ", 10)) {
00528     const char *buf = str + 10;
00529     long langid = strtol(buf, NULL, 16);
00530     if (langid > (long)UINT16_MAX || langid < 0) {
00531       error("Invalid winlangid %s", buf);
00532     }
00533     _lang_winlangid = (uint16)langid;
00534   } else if (!memcmp(str, "grflangid ", 10)) {
00535     const char *buf = str + 10;
00536     long langid = strtol(buf, NULL, 16);
00537     if (langid >= 0x7F || langid < 0) {
00538       error("Invalid grflangid %s", buf);
00539     }
00540     _lang_newgrflangid = (uint8)langid;
00541   } else if (!memcmp(str, "gender ", 7)) {
00542     char *buf = str + 7;
00543 
00544     for (;;) {
00545       const char *s = ParseWord(&buf);
00546 
00547       if (s == NULL) break;
00548       if (_numgenders >= MAX_NUM_GENDER) error("Too many genders, max %d", MAX_NUM_GENDER);
00549       strecpy(_genders[_numgenders], s, lastof(_genders[_numgenders]));
00550       _numgenders++;
00551     }
00552   } else if (!memcmp(str, "case ", 5)) {
00553     char *buf = str + 5;
00554 
00555     for (;;) {
00556       const char *s = ParseWord(&buf);
00557 
00558       if (s == NULL) break;
00559       if (_numcases >= MAX_NUM_CASES) error("Too many cases, max %d", MAX_NUM_CASES);
00560       strecpy(_cases[_numcases], s, lastof(_cases[_numcases]));
00561       _numcases++;
00562     }
00563   } else {
00564     error("unknown pragma '%s'", str);
00565   }
00566 }
00567 
00568 static void ExtractCommandString(ParsedCommandStruct *p, const char *s, bool warnings)
00569 {
00570   char param[100];
00571   int argno;
00572   int argidx = 0;
00573   int casei;
00574 
00575   memset(p, 0, sizeof(*p));
00576 
00577   for (;;) {
00578     /* read until next command from a. */
00579     const CmdStruct *ar = ParseCommandString(&s, param, &argno, &casei);
00580 
00581     if (ar == NULL) break;
00582 
00583     /* Sanity checking */
00584     if (argno != -1 && ar->consumes == 0) error("Non consumer param can't have a paramindex");
00585 
00586     if (ar->consumes) {
00587       if (argno != -1) argidx = argno;
00588       if (argidx < 0 || (uint)argidx >= lengthof(p->cmd)) error("invalid param idx %d", argidx);
00589       if (p->cmd[argidx] != NULL && p->cmd[argidx] != ar) error("duplicate param idx %d", argidx);
00590 
00591       p->cmd[argidx++] = ar;
00592     } else if (!(ar->flags & C_DONTCOUNT)) { // Ignore some of them
00593       if (p->np >= lengthof(p->pairs)) error("too many commands in string, max " PRINTF_SIZE, lengthof(p->pairs));
00594       p->pairs[p->np].a = ar;
00595       p->pairs[p->np].v = param[0] != '\0' ? strdup(param) : "";
00596       p->np++;
00597     }
00598   }
00599 }
00600 
00601 
00602 static const CmdStruct *TranslateCmdForCompare(const CmdStruct *a)
00603 {
00604   if (a == NULL) return NULL;
00605 
00606   if (strcmp(a->cmd, "STRING1") == 0 ||
00607       strcmp(a->cmd, "STRING2") == 0 ||
00608       strcmp(a->cmd, "STRING3") == 0 ||
00609       strcmp(a->cmd, "STRING4") == 0 ||
00610       strcmp(a->cmd, "STRING5") == 0 ||
00611       strcmp(a->cmd, "RAW_STRING") == 0) {
00612     return FindCmd("STRING", 6);
00613   }
00614 
00615   return a;
00616 }
00617 
00618 
00619 static bool CheckCommandsMatch(char *a, char *b, const char *name)
00620 {
00621   /* If we're not translating, i.e. we're compiling the base language,
00622    * it is pointless to do all these checks as it'll always be correct.
00623    * After all, all checks are based on the base language.
00624    */
00625   if (!_translation) return true;
00626 
00627   ParsedCommandStruct templ;
00628   ParsedCommandStruct lang;
00629   bool result = true;
00630 
00631   ExtractCommandString(&templ, b, true);
00632   ExtractCommandString(&lang, a, true);
00633 
00634   /* For each string in templ, see if we find it in lang */
00635   if (templ.np != lang.np) {
00636     strgen_warning("%s: template string and language string have a different # of commands", name);
00637     result = false;
00638   }
00639 
00640   for (uint i = 0; i < templ.np; i++) {
00641     /* see if we find it in lang, and zero it out */
00642     bool found = false;
00643     for (uint j = 0; j < lang.np; j++) {
00644       if (templ.pairs[i].a == lang.pairs[j].a &&
00645           strcmp(templ.pairs[i].v, lang.pairs[j].v) == 0) {
00646         /* it was found in both. zero it out from lang so we don't find it again */
00647         lang.pairs[j].a = NULL;
00648         found = true;
00649         break;
00650       }
00651     }
00652 
00653     if (!found) {
00654       strgen_warning("%s: command '%s' exists in template file but not in language file", name, templ.pairs[i].a->cmd);
00655       result = false;
00656     }
00657   }
00658 
00659   /* if we reach here, all non consumer commands match up.
00660    * Check if the non consumer commands match up also. */
00661   for (uint i = 0; i < lengthof(templ.cmd); i++) {
00662     if (TranslateCmdForCompare(templ.cmd[i]) != lang.cmd[i]) {
00663       strgen_warning("%s: Param idx #%d '%s' doesn't match with template command '%s'", name, i,
00664         lang.cmd[i]  == NULL ? "<empty>" : TranslateCmdForCompare(lang.cmd[i])->cmd,
00665         templ.cmd[i] == NULL ? "<empty>" : templ.cmd[i]->cmd);
00666       result = false;
00667     }
00668   }
00669 
00670   return result;
00671 }
00672 
00673 static void HandleString(char *str, bool master)
00674 {
00675   if (*str == '#') {
00676     if (str[1] == '#' && str[2] != '#') HandlePragma(str + 2);
00677     return;
00678   }
00679 
00680   /* Ignore comments & blank lines */
00681   if (*str == ';' || *str == ' ' || *str == '\0') return;
00682 
00683   char *s = strchr(str, ':');
00684   if (s == NULL) {
00685     strgen_error("Line has no ':' delimiter");
00686     return;
00687   }
00688 
00689   char *t;
00690   /* Trim spaces.
00691    * After this str points to the command name, and s points to the command contents */
00692   for (t = s; t > str && (t[-1] == ' ' || t[-1] == '\t'); t--) {}
00693   *t = 0;
00694   s++;
00695 
00696   /* Check string is valid UTF-8 */
00697   const char *tmp;
00698   for (tmp = s; *tmp != '\0';) {
00699     size_t len = Utf8Validate(tmp);
00700     if (len == 0) error("Invalid UTF-8 sequence in '%s'", s);
00701     tmp += len;
00702   }
00703 
00704   /* Check if the string has a case..
00705    * The syntax for cases is IDENTNAME.case */
00706   char *casep = strchr(str, '.');
00707   if (casep) *casep++ = '\0';
00708 
00709   /* Check if this string already exists.. */
00710   LangString *ent = HashFind(str);
00711 
00712   if (master) {
00713     if (ent != NULL && casep == NULL) {
00714       strgen_error("String name '%s' is used multiple times", str);
00715       return;
00716     }
00717 
00718     if (ent == NULL && casep != NULL) {
00719       strgen_error("Base string name '%s' doesn't exist yet. Define it before defining a case.", str);
00720       return;
00721     }
00722 
00723     if (ent == NULL) {
00724       if (_strings[_next_string_id]) {
00725         strgen_error("String ID 0x%X for '%s' already in use by '%s'", _next_string_id, str, _strings[_next_string_id]->name);
00726         return;
00727       }
00728 
00729       /* Allocate a new LangString */
00730       ent = CallocT<LangString>(1);
00731       _strings[_next_string_id] = ent;
00732       ent->index = _next_string_id++;
00733       ent->name = strdup(str);
00734       ent->line = _cur_line;
00735 
00736       HashAdd(str, ent);
00737     }
00738 
00739     if (casep != NULL) {
00740       Case *c = MallocT<Case>(1);
00741 
00742       c->caseidx = ResolveCaseName(casep, strlen(casep));
00743       c->string = strdup(s);
00744       c->next = ent->english_case;
00745       ent->english_case = c;
00746     } else {
00747       ent->english = strdup(s);
00748     }
00749 
00750   } else {
00751     if (ent == NULL) {
00752       strgen_warning("String name '%s' does not exist in master file", str);
00753       return;
00754     }
00755 
00756     if (ent->translated && casep == NULL) {
00757       strgen_error("String name '%s' is used multiple times", str);
00758       return;
00759     }
00760 
00761     if (s[0] == ':' && s[1] == '\0' && casep == NULL) {
00762       /* Special syntax :: means we should just inherit the master string */
00763       ent->translated = strdup(ent->english);
00764     } else {
00765       /* make sure that the commands match */
00766       if (!CheckCommandsMatch(s, ent->english, str)) return;
00767 
00768       if (casep != NULL) {
00769         Case *c = MallocT<Case>(1);
00770 
00771         c->caseidx = ResolveCaseName(casep, strlen(casep));
00772         c->string = strdup(s);
00773         c->next = ent->translated_case;
00774         ent->translated_case = c;
00775       } else {
00776         ent->translated = strdup(s);
00777         /* If the string was translated, use the line from the
00778          * translated language so errors in the translated file
00779          * are properly referenced to. */
00780         ent->line = _cur_line;
00781       }
00782     }
00783   }
00784 }
00785 
00786 
00787 static void rstrip(char *buf)
00788 {
00789   int i = strlen(buf);
00790   while (i > 0 && (buf[i - 1] == '\r' || buf[i - 1] == '\n' || buf[i - 1] == ' ')) i--;
00791   buf[i] = '\0';
00792 }
00793 
00794 
00795 static void ParseFile(const char *file, bool english)
00796 {
00797   FILE *in;
00798   char buf[2048];
00799 
00800   /* Only look at the final filename to determine whether it's be base language or not */
00801   const char *cur_file = strrchr(_file, PATHSEPCHAR);
00802   const char *next_file = strrchr(file, PATHSEPCHAR);
00803   _translation = next_file != NULL && cur_file != NULL && strcmp(cur_file, next_file) != 0;
00804   _file = file;
00805 
00806   /* For each new file we parse, reset the genders, and language codes */
00807   _numgenders = 0;
00808   _lang_name[0] = _lang_ownname[0] = _lang_isocode[0] = '\0';
00809   strecpy(_lang_digit_group_separator, ",", lastof(_lang_digit_group_separator));
00810   strecpy(_lang_digit_group_separator_currency, ",", lastof(_lang_digit_group_separator_currency));
00811   strecpy(_lang_digit_decimal_separator, ".", lastof(_lang_digit_decimal_separator));
00812   _lang_textdir = TD_LTR;
00813   _lang_winlangid = 0x0000; // neutral language code
00814   _lang_newgrflangid = 0; // standard english
00815   /* TODO:!! We can't reset the cases. In case the translated strings
00816    * derive some strings from english.... */
00817 
00818   in = fopen(file, "r");
00819   if (in == NULL) error("Cannot open file");
00820   _cur_line = 1;
00821   while (fgets(buf, sizeof(buf), in) != NULL) {
00822     rstrip(buf);
00823     HandleString(buf, english);
00824     _cur_line++;
00825   }
00826   fclose(in);
00827 
00828   if (StrEmpty(_lang_name) || StrEmpty(_lang_ownname) || StrEmpty(_lang_isocode)) {
00829     error("Language must include ##name, ##ownname and ##isocode");
00830   }
00831 }
00832 
00833 
00834 static uint32 MyHashStr(uint32 hash, const char *s)
00835 {
00836   for (; *s != '\0'; s++) {
00837     hash = ROL(hash, 3) ^ *s;
00838     hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
00839   }
00840   return hash;
00841 }
00842 
00843 
00844 /* make a hash of the file to get a unique "version number" */
00845 static void MakeHashOfStrings()
00846 {
00847   uint32 hash = 0;
00848   uint i;
00849 
00850   for (i = 0; i != lengthof(_strings); i++) {
00851     const LangString *ls = _strings[i];
00852 
00853     if (ls != NULL) {
00854       const CmdStruct *cs;
00855       const char *s;
00856       char buf[256];
00857       int argno;
00858       int casei;
00859 
00860       s = ls->name;
00861       hash ^= i * 0x717239;
00862       hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
00863       hash = MyHashStr(hash, s + 1);
00864 
00865       s = ls->english;
00866       while ((cs = ParseCommandString(&s, buf, &argno, &casei)) != NULL) {
00867         if (cs->flags & C_DONTCOUNT) continue;
00868 
00869         hash ^= (cs - _cmd_structs) * 0x1234567;
00870         hash = (hash & 1 ? hash >> 1 ^ 0xF00BAA4 : hash >> 1);
00871       }
00872     }
00873   }
00874   _hash = hash;
00875 }
00876 
00877 
00878 static uint CountInUse(uint grp)
00879 {
00880   int i;
00881 
00882   for (i = 0x800; --i >= 0;) if (_strings[(grp << 11) + i] != NULL) break;
00883   return i + 1;
00884 }
00885 
00886 
00887 bool CompareFiles(const char *n1, const char *n2)
00888 {
00889   FILE *f2 = fopen(n2, "rb");
00890   if (f2 == NULL) return false;
00891 
00892   FILE *f1 = fopen(n1, "rb");
00893   if (f1 == NULL) error("can't open %s", n1);
00894 
00895   size_t l1, l2;
00896   do {
00897     char b1[4096];
00898     char b2[4096];
00899     l1 = fread(b1, 1, sizeof(b1), f1);
00900     l2 = fread(b2, 1, sizeof(b2), f2);
00901 
00902     if (l1 != l2 || memcmp(b1, b2, l1)) {
00903       fclose(f2);
00904       fclose(f1);
00905       return false;
00906     }
00907   } while (l1);
00908 
00909   fclose(f2);
00910   fclose(f1);
00911   return true;
00912 }
00913 
00914 
00915 static void WriteStringsH(const char *filename)
00916 {
00917   int next = -1;
00918 
00919   FILE *out = fopen("tmp.xxx", "w");
00920   if (out == NULL) error("can't open tmp.xxx");
00921 
00922   fprintf(out, "/* This file is automatically generated. Do not modify */\n\n");
00923   fprintf(out, "#ifndef TABLE_STRINGS_H\n");
00924   fprintf(out, "#define TABLE_STRINGS_H\n");
00925 
00926   for (int i = 0; i != lengthof(_strings); i++) {
00927     if (_strings[i] != NULL) {
00928       if (next != i) fprintf(out, "\n");
00929       fprintf(out, "static const StringID %s = 0x%X;\n", _strings[i]->name, i);
00930       next = i + 1;
00931     }
00932   }
00933 
00934   fprintf(out, "\nstatic const StringID STR_LAST_STRINGID = 0x%X;\n", next - 1);
00935 
00936   fprintf(out,
00937     "\nenum {\n"
00938     "\tLANGUAGE_PACK_IDENT = 0x474E414C, // Big Endian value for 'LANG' (LE is 0x 4C 41 4E 47)\n"
00939     "\tLANGUAGE_PACK_VERSION = 0x%X,\n"
00940     "};\n", (uint)_hash
00941   );
00942 
00943   fprintf(out, "\n#endif /* TABLE_STRINGS_H */\n");
00944 
00945   fclose(out);
00946 
00947   if (CompareFiles("tmp.xxx", filename)) {
00948     /* files are equal. tmp.xxx is not needed */
00949     unlink("tmp.xxx");
00950   } else {
00951     /* else rename tmp.xxx into filename */
00952 #if defined(WIN32) || defined(WIN64)
00953     unlink(filename);
00954 #endif
00955     if (rename("tmp.xxx", filename) == -1) error("rename() failed");
00956   }
00957 }
00958 
00959 static int TranslateArgumentIdx(int argidx, int offset)
00960 {
00961   int sum;
00962 
00963   if (argidx < 0 || (uint)argidx >= lengthof(_cur_pcs.cmd)) {
00964     error("invalid argidx %d", argidx);
00965   }
00966   const CmdStruct *cs = _cur_pcs.cmd[argidx];
00967   if (cs != NULL && cs->consumes <= offset) {
00968     error("invalid argidx offset %d:%d", argidx, offset);
00969   }
00970 
00971   if (_cur_pcs.cmd[argidx] == NULL) {
00972     error("no command for this argidx %d", argidx);
00973   }
00974 
00975   for (int i = sum = 0; i < argidx; i++) {
00976     const CmdStruct *cs = _cur_pcs.cmd[i];
00977 
00978     sum += (cs != NULL) ? cs->consumes : 1;
00979   }
00980 
00981   return sum + offset;
00982 }
00983 
00984 static void PutArgidxCommand()
00985 {
00986   PutUtf8(SCC_ARG_INDEX);
00987   PutByte(TranslateArgumentIdx(_cur_argidx));
00988 }
00989 
00990 
00991 static void PutCommandString(const char *str)
00992 {
00993   _cur_argidx = 0;
00994 
00995   while (*str != '\0') {
00996     /* Process characters as they are until we encounter a { */
00997     if (*str != '{') {
00998       PutByte(*str++);
00999       continue;
01000     }
01001 
01002     char param[256];
01003     int argno;
01004     int casei;
01005     const CmdStruct *cs = ParseCommandString(&str, param, &argno, &casei);
01006     if (cs == NULL) break;
01007 
01008     if (casei != -1) {
01009       PutUtf8(SCC_SETCASE); // {SETCASE}
01010       PutByte(casei);
01011     }
01012 
01013     /* For params that consume values, we need to handle the argindex properly */
01014     if (cs->consumes > 0) {
01015       /* Check if we need to output a move-param command */
01016       if (argno != -1 && argno != _cur_argidx) {
01017         _cur_argidx = argno;
01018         PutArgidxCommand();
01019       }
01020 
01021       /* Output the one from the master string... it's always accurate. */
01022       cs = _cur_pcs.cmd[_cur_argidx++];
01023       if (cs == NULL) {
01024         error("%s: No argument exists at position %d", _cur_ident, _cur_argidx - 1);
01025       }
01026     }
01027 
01028     cs->proc(param, cs->value);
01029   }
01030 }
01031 
01032 static void WriteLength(FILE *f, uint length)
01033 {
01034   if (length < 0xC0) {
01035     fputc(length, f);
01036   } else if (length < 0x4000) {
01037     fputc((length >> 8) | 0xC0, f);
01038     fputc(length & 0xFF, f);
01039   } else {
01040     error("string too long");
01041   }
01042 }
01043 
01044 
01045 static void WriteLangfile(const char *filename)
01046 {
01047   uint in_use[32];
01048   LanguagePackHeader hdr;
01049 
01050   FILE *f = fopen(filename, "wb");
01051   if (f == NULL) error("can't open %s", filename);
01052 
01053   memset(&hdr, 0, sizeof(hdr));
01054   for (int i = 0; i != 32; i++) {
01055     uint n = CountInUse(i);
01056 
01057     in_use[i] = n;
01058     hdr.offsets[i] = TO_LE16(n);
01059   }
01060 
01061   /* see line 655: fprintf(..."\tLANGUAGE_PACK_IDENT = 0x474E414C,...) */
01062   hdr.ident = TO_LE32(0x474E414C); // Big Endian value for 'LANG'
01063   hdr.version = TO_LE32(_hash);
01064   hdr.plural_form = _lang_pluralform;
01065   hdr.text_dir = _lang_textdir;
01066   hdr.winlangid = TO_LE16(_lang_winlangid);
01067   hdr.newgrflangid = _lang_newgrflangid;
01068   strecpy(hdr.name, _lang_name, lastof(hdr.name));
01069   strecpy(hdr.own_name, _lang_ownname, lastof(hdr.own_name));
01070   strecpy(hdr.isocode, _lang_isocode, lastof(hdr.isocode));
01071   strecpy(hdr.digit_group_separator, _lang_digit_group_separator, lastof(hdr.digit_group_separator));
01072   strecpy(hdr.digit_group_separator_currency, _lang_digit_group_separator_currency, lastof(hdr.digit_group_separator_currency));
01073   strecpy(hdr.digit_decimal_separator, _lang_digit_decimal_separator, lastof(hdr.digit_decimal_separator));
01074 
01075   fwrite(&hdr, sizeof(hdr), 1, f);
01076 
01077   for (int i = 0; i != 32; i++) {
01078     for (uint j = 0; j != in_use[i]; j++) {
01079       const LangString *ls = _strings[(i << 11) + j];
01080       const Case *casep;
01081       const char *cmdp;
01082 
01083       /* For undefined strings, just set that it's an empty string */
01084       if (ls == NULL) {
01085         WriteLength(f, 0);
01086         continue;
01087       }
01088 
01089       _cur_ident = ls->name;
01090       _cur_line = ls->line;
01091 
01092       /* Produce a message if a string doesn't have a translation. */
01093       if (_show_todo > 0 && ls->translated == NULL) {
01094         if ((_show_todo & 2) != 0) {
01095           strgen_warning("'%s' is untranslated", ls->name);
01096         }
01097         if ((_show_todo & 1) != 0) {
01098           const char *s = "<TODO> ";
01099           while (*s != '\0') PutByte(*s++);
01100         }
01101       }
01102 
01103       /* Extract the strings and stuff from the english command string */
01104       ExtractCommandString(&_cur_pcs, ls->english, false);
01105 
01106       if (ls->translated_case != NULL || ls->translated != NULL) {
01107         casep = ls->translated_case;
01108         cmdp = ls->translated;
01109       } else {
01110         casep = ls->english_case;
01111         cmdp = ls->english;
01112       }
01113 
01114       _translated = _masterlang || (cmdp != ls->english);
01115 
01116       if (casep != NULL) {
01117         const Case *c;
01118         uint num;
01119 
01120         /* Need to output a case-switch.
01121          * It has this format
01122          * <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
01123          * Each LEN is printed using 2 bytes in big endian order. */
01124         PutUtf8(SCC_SWITCH_CASE);
01125         /* Count the number of cases */
01126         for (num = 0, c = casep; c; c = c->next) num++;
01127         PutByte(num);
01128 
01129         /* Write each case */
01130         for (c = casep; c != NULL; c = c->next) {
01131           int pos;
01132 
01133           PutByte(c->caseidx);
01134           /* Make some space for the 16-bit length */
01135           pos = _put_pos;
01136           PutByte(0);
01137           PutByte(0);
01138           /* Write string */
01139           PutCommandString(c->string);
01140           PutByte(0); // terminate with a zero
01141           /* Fill in the length */
01142           _put_buf[pos + 0] = GB(_put_pos - (pos + 2), 8, 8);
01143           _put_buf[pos + 1] = GB(_put_pos - (pos + 2), 0, 8);
01144         }
01145       }
01146 
01147       if (cmdp != NULL) PutCommandString(cmdp);
01148 
01149       WriteLength(f, _put_pos);
01150       fwrite(_put_buf, 1, _put_pos, f);
01151       _put_pos = 0;
01152     }
01153   }
01154 
01155   fputc(0, f);
01156   fclose(f);
01157 }
01158 
01160 static inline void ottd_mkdir(const char *directory)
01161 {
01162 #if defined(WIN32) || defined(__WATCOMC__)
01163     mkdir(directory);
01164 #else
01165     mkdir(directory, 0755);
01166 #endif
01167 }
01168 
01172 static inline char *mkpath(char *buf, size_t buflen, const char *path, const char *file)
01173 {
01174   ttd_strlcpy(buf, path, buflen); // copy directory into buffer
01175 
01176   char *p = strchr(buf, '\0'); // add path seperator if necessary
01177   if (p[-1] != PATHSEPCHAR && (size_t)(p - buf) + 1 < buflen) *p++ = PATHSEPCHAR;
01178   ttd_strlcpy(p, file, buflen - (size_t)(p - buf)); // catenate filename at end of buffer
01179   return buf;
01180 }
01181 
01182 #if defined(__MINGW32__)
01183 
01188 static inline char *replace_pathsep(char *s)
01189 {
01190   for (char *c = s; *c != '\0'; c++) if (*c == '/') *c = '\\';
01191   return s;
01192 }
01193 #else
01194 static inline char *replace_pathsep(char *s) { return s; }
01195 #endif
01196 
01197 int CDECL main(int argc, char *argv[])
01198 {
01199   char pathbuf[MAX_PATH];
01200   const char *src_dir = ".";
01201   const char *dest_dir = NULL;
01202 
01203   while (argc > 1 && *argv[1] == '-') {
01204     if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) {
01205       puts("$Revision: 18727 $");
01206       return 0;
01207     }
01208 
01209     if (strcmp(argv[1], "-export-commands") == 0) {
01210       printf("args\tflags\tcommand\treplacement\n");
01211       for (const CmdStruct *cs = _cmd_structs; cs < endof(_cmd_structs); cs++) {
01212         char flags;
01213         switch (cs->value) {
01214           case 0x200E: case 0x200F: // Implicit BIDI controls
01215           case 0x202A: case 0x202B: case 0x202C: case 0x202D: case 0x202E: // Explicit BIDI controls
01216           case 0xA0: // Non breaking space
01217           case '\n': // Newlines may be added too
01218           case '{':  // This special
01219             /* This command may be in the translation when it is not in base */
01220             flags = 'i';
01221             break;
01222 
01223           default:
01224             if (cs->proc == EmitGender) {
01225               flags = 'g'; // Command needs number of parameters defined by number of genders
01226             } else if (cs->proc == EmitPlural) {
01227               flags = 'p'; // Command needs number of parameters defined by plural value
01228             } else {
01229               flags = '0'; // Command needs no parameters
01230             }
01231         }
01232         printf("%i\t%c\t\"%s\"\t\"%s\"\n", cs->consumes, flags, cs->cmd, strstr(cs->cmd, "STRING") ? "STRING" : cs->cmd);
01233       }
01234       return 0;
01235     }
01236 
01237     if (strcmp(argv[1], "-export-plurals") == 0) {
01238       printf("count\tdescription\n");
01239       for (const PluralForm *pf = _plural_forms; pf < endof(_plural_forms); pf++) {
01240         printf("%i\t\"%s\"\n", pf->plural_count, pf->description);
01241       }
01242       return 0;
01243     }
01244 
01245     if (strcmp(argv[1], "-export-pragmas") == 0) {
01246       printf("name\tflags\tdefault\tdescription\n");
01247       for (size_t i = 0; i < lengthof(_pragmas); i++) {
01248         printf("\"%s\"\t%s\t\"%s\"\t\"%s\"\n",
01249             _pragmas[i][0], _pragmas[i][1], _pragmas[i][2], _pragmas[i][3]);
01250       }
01251       return 0;
01252     }
01253 
01254     if (strcmp(argv[1], "-t") == 0 || strcmp(argv[1], "--todo") == 0) {
01255       _show_todo |= 1;
01256       argc--, argv++;
01257       continue;
01258     }
01259 
01260     if (strcmp(argv[1], "-w") == 0 || strcmp(argv[1], "--warning") == 0) {
01261       _show_todo |= 2;
01262       argc--, argv++;
01263       continue;
01264     }
01265 
01266     if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) {
01267       puts(
01268         "strgen - $Revision: 18727 $\n"
01269         " -v | --version    print version information and exit\n"
01270         " -t | --todo       replace any untranslated strings with '<TODO>'\n"
01271         " -w | --warning    print a warning for any untranslated strings\n"
01272         " -h | -? | --help  print this help message and exit\n"
01273         " -s | --source_dir search for english.txt in the specified directory\n"
01274         " -d | --dest_dir   put output file in the specified directory, create if needed\n"
01275         " -export-commands  export all commands and exit\n"
01276         " -export-plurals   export all plural forms and exit\n"
01277         " -export-pragmas   export all pragmas and exit\n"
01278         " Run without parameters and strgen will search for english.txt and parse it,\n"
01279         " creating strings.h. Passing an argument, strgen will translate that language\n"
01280         " file using english.txt as a reference and output <language>.lng."
01281       );
01282       return 0;
01283     }
01284 
01285     if (argc > 2 && (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--source_dir") == 0)) {
01286       src_dir = replace_pathsep(argv[2]);
01287       argc -= 2, argv += 2;
01288       continue;
01289     }
01290 
01291     if (argc > 2 && (strcmp(argv[1], "-d") == 0 || strcmp(argv[1], "--dest_dir") == 0)) {
01292       dest_dir = replace_pathsep(argv[2]);
01293       argc -= 2, argv += 2;
01294       continue;
01295     }
01296 
01297     fprintf(stderr, "Invalid arguments\n");
01298     return 0;
01299   }
01300 
01301   if (dest_dir == NULL) dest_dir = src_dir; // if dest_dir is not specified, it equals src_dir
01302 
01303   /* strgen has two modes of operation. If no (free) arguments are passed
01304    * strgen generates strings.h to the destination directory. If it is supplied
01305    * with a (free) parameter the program will translate that language to destination
01306    * directory. As input english.txt is parsed from the source directory */
01307   if (argc == 1) {
01308     mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
01309 
01310     /* parse master file */
01311     _masterlang = true;
01312     ParseFile(pathbuf, true);
01313     MakeHashOfStrings();
01314     if (_errors) return 1;
01315 
01316     /* write strings.h */
01317     ottd_mkdir(dest_dir);
01318     mkpath(pathbuf, lengthof(pathbuf), dest_dir, "strings.h");
01319     WriteStringsH(pathbuf);
01320   } else if (argc == 2) {
01321     char *r;
01322 
01323     mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
01324 
01325     /* parse master file and check if target file is correct */
01326     _masterlang = false;
01327     ParseFile(pathbuf, true);
01328     MakeHashOfStrings();
01329     ParseFile(replace_pathsep(argv[1]), false); // target file
01330     if (_errors) return 1;
01331 
01332     /* get the targetfile, strip any directories and append to destination path */
01333     r = strrchr(argv[1], PATHSEPCHAR);
01334     mkpath(pathbuf, lengthof(pathbuf), dest_dir, (r != NULL) ? &r[1] : argv[1]);
01335 
01336     /* rename the .txt (input-extension) to .lng */
01337     r = strrchr(pathbuf, '.');
01338     if (r == NULL || strcmp(r, ".txt") != 0) r = strchr(pathbuf, '\0');
01339     ttd_strlcpy(r, ".lng", (size_t)(r - pathbuf));
01340     WriteLangfile(pathbuf);
01341 
01342     /* if showing warnings, print a summary of the language */
01343     if ((_show_todo & 2) != 0) {
01344       fprintf(stdout, "%d warnings and %d errors for %s\n", _warnings, _errors, pathbuf);
01345     }
01346   } else {
01347     fprintf(stderr, "Invalid arguments\n");
01348   }
01349 
01350   return 0;
01351 }

Generated on Thu Feb 4 17:20:28 2010 for OpenTTD by  doxygen 1.5.6