# strgen.cpp

Go to the documentation of this file.

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