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 }

Generated on Sun Nov 15 15:40:15 2009 for OpenTTD by  doxygen 1.5.6