00001
00002
00003
00004
00005
00006
00007
00008
00009
00012 #include "../stdafx.h"
00013 #include "../core/endian_func.hpp"
00014 #include "../string_func.h"
00015 #include "../table/control_codes.h"
00016
00017 #include "strgen.h"
00018
00019
00020 #include "../table/strgen_tables.h"
00021
00022
00023
00024 static bool _translated;
00025 static bool _translation;
00026 const char *_file = "(unknown file)";
00027 int _cur_line;
00028 int _errors, _warnings, _show_todo;
00029 LanguagePackHeader _lang;
00030
00031 static const ptrdiff_t MAX_COMMAND_PARAM_SIZE = 100;
00032 static const CmdStruct *ParseCommandString(const char **str, char *param, int *argno, int *casei);
00033
00040 Case::Case(int caseidx, const char *string, Case *next) :
00041 caseidx(caseidx), string(strdup(string)), next(next)
00042 {
00043 }
00044
00046 Case::~Case()
00047 {
00048 free(this->string);
00049 delete this->next;
00050 }
00051
00059 LangString::LangString(const char *name, const char *english, int index, int line) :
00060 name(strdup(name)), english(strdup(english)), translated(NULL),
00061 hash_next(0), index(index), line(line), translated_case(NULL)
00062 {
00063 }
00064
00066 LangString::~LangString()
00067 {
00068 free(this->name);
00069 free(this->english);
00070 free(this->translated);
00071 delete this->translated_case;
00072 }
00073
00075 void LangString::FreeTranslation()
00076 {
00077 free(this->translated);
00078 this->translated = NULL;
00079
00080 delete this->translated_case;
00081 this->translated_case = NULL;
00082 }
00083
00088 StringData::StringData(size_t tabs) : tabs(tabs), max_strings(tabs * TAB_SIZE)
00089 {
00090 this->strings = CallocT<LangString *>(max_strings);
00091 this->hash_heads = CallocT<uint16>(max_strings);
00092 this->next_string_id = 0;
00093 }
00094
00096 StringData::~StringData()
00097 {
00098 for (size_t i = 0; i < this->max_strings; i++) delete this->strings[i];
00099 free(this->strings);
00100 free(this->hash_heads);
00101 }
00102
00104 void StringData::FreeTranslation()
00105 {
00106 for (size_t i = 0; i < this->max_strings; i++) {
00107 LangString *ls = this->strings[i];
00108 if (ls != NULL) ls->FreeTranslation();
00109 }
00110 }
00111
00117 uint StringData::HashStr(const char *s) const
00118 {
00119 uint hash = 0;
00120 for (; *s != '\0'; s++) hash = ROL(hash, 3) ^ *s;
00121 return hash % this->max_strings;
00122 }
00123
00129 void StringData::Add(const char *s, LangString *ls)
00130 {
00131 uint hash = this->HashStr(s);
00132 ls->hash_next = this->hash_heads[hash];
00133
00134 this->hash_heads[hash] = ls->index + 1;
00135 this->strings[ls->index] = ls;
00136 }
00137
00143 LangString *StringData::Find(const char *s)
00144 {
00145 int idx = this->hash_heads[this->HashStr(s)];
00146
00147 while (--idx >= 0) {
00148 LangString *ls = this->strings[idx];
00149
00150 if (strcmp(ls->name, s) == 0) return ls;
00151 idx = ls->hash_next;
00152 }
00153 return NULL;
00154 }
00155
00162 uint StringData::VersionHashStr(uint hash, const char *s) const
00163 {
00164 for (; *s != '\0'; s++) {
00165 hash = ROL(hash, 3) ^ *s;
00166 hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
00167 }
00168 return hash;
00169 }
00170
00175 uint StringData::Version() const
00176 {
00177 uint hash = 0;
00178
00179 for (size_t i = 0; i < this->max_strings; i++) {
00180 const LangString *ls = this->strings[i];
00181
00182 if (ls != NULL) {
00183 const CmdStruct *cs;
00184 const char *s;
00185 char buf[MAX_COMMAND_PARAM_SIZE];
00186 int argno;
00187 int casei;
00188
00189 s = ls->name;
00190 hash ^= i * 0x717239;
00191 hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
00192 hash = this->VersionHashStr(hash, s + 1);
00193
00194 s = ls->english;
00195 while ((cs = ParseCommandString(&s, buf, &argno, &casei)) != NULL) {
00196 if (cs->flags & C_DONTCOUNT) continue;
00197
00198 hash ^= (cs - _cmd_structs) * 0x1234567;
00199 hash = (hash & 1 ? hash >> 1 ^ 0xF00BAA4 : hash >> 1);
00200 }
00201 }
00202 }
00203
00204 return hash;
00205 }
00206
00211 uint StringData::CountInUse(uint tab) const
00212 {
00213 int i;
00214 for (i = TAB_SIZE; --i >= 0;) if (this->strings[(tab * TAB_SIZE) + i] != NULL) break;
00215 return i + 1;
00216 }
00217
00218 static const char *_cur_ident;
00219
00220 struct CmdPair {
00221 const CmdStruct *a;
00222 const char *v;
00223 };
00224
00225 struct ParsedCommandStruct {
00226 uint np;
00227 CmdPair pairs[32];
00228 const CmdStruct *cmd[32];
00229 };
00230
00231
00232 static ParsedCommandStruct _cur_pcs;
00233 static int _cur_argidx;
00234
00236 struct Buffer : SmallVector<byte, 256> {
00241 void AppendByte(byte value)
00242 {
00243 *this->Append() = value;
00244 }
00245
00250 void AppendUtf8(uint32 value)
00251 {
00252 if (value < 0x80) {
00253 *this->Append() = value;
00254 } else if (value < 0x800) {
00255 *this->Append() = 0xC0 + GB(value, 6, 5);
00256 *this->Append() = 0x80 + GB(value, 0, 6);
00257 } else if (value < 0x10000) {
00258 *this->Append() = 0xE0 + GB(value, 12, 4);
00259 *this->Append() = 0x80 + GB(value, 6, 6);
00260 *this->Append() = 0x80 + GB(value, 0, 6);
00261 } else if (value < 0x110000) {
00262 *this->Append() = 0xF0 + GB(value, 18, 3);
00263 *this->Append() = 0x80 + GB(value, 12, 6);
00264 *this->Append() = 0x80 + GB(value, 6, 6);
00265 *this->Append() = 0x80 + GB(value, 0, 6);
00266 } else {
00267 strgen_warning("Invalid unicode value U+0x%X", value);
00268 }
00269 }
00270 };
00271
00272 size_t Utf8Validate(const char *s)
00273 {
00274 uint32 c;
00275
00276 if (!HasBit(s[0], 7)) {
00277
00278 return 1;
00279 } else if (GB(s[0], 5, 3) == 6 && IsUtf8Part(s[1])) {
00280
00281 c = GB(s[0], 0, 5) << 6 | GB(s[1], 0, 6);
00282 if (c >= 0x80) return 2;
00283 } else if (GB(s[0], 4, 4) == 14 && IsUtf8Part(s[1]) && IsUtf8Part(s[2])) {
00284
00285 c = GB(s[0], 0, 4) << 12 | GB(s[1], 0, 6) << 6 | GB(s[2], 0, 6);
00286 if (c >= 0x800) return 3;
00287 } else if (GB(s[0], 3, 5) == 30 && IsUtf8Part(s[1]) && IsUtf8Part(s[2]) && IsUtf8Part(s[3])) {
00288
00289 c = GB(s[0], 0, 3) << 18 | GB(s[1], 0, 6) << 12 | GB(s[2], 0, 6) << 6 | GB(s[3], 0, 6);
00290 if (c >= 0x10000 && c <= 0x10FFFF) return 4;
00291 }
00292
00293 return 0;
00294 }
00295
00296
00297 void EmitSingleChar(Buffer *buffer, char *buf, int value)
00298 {
00299 if (*buf != '\0') strgen_warning("Ignoring trailing letters in command");
00300 buffer->AppendUtf8(value);
00301 }
00302
00303
00304
00305
00306
00307
00308
00309
00310 bool ParseRelNum(char **buf, int *value, int *offset)
00311 {
00312 const char *s = *buf;
00313 char *end;
00314 bool rel = false;
00315
00316 while (*s == ' ' || *s == '\t') s++;
00317 if (*s == '+') {
00318 rel = true;
00319 s++;
00320 }
00321 int v = strtol(s, &end, 0);
00322 if (end == s) return false;
00323 if (rel || v < 0) {
00324 *value += v;
00325 } else {
00326 *value = v;
00327 }
00328 if (offset != NULL && *end == ':') {
00329
00330 s = end + 1;
00331 *offset = strtol(s, &end, 0);
00332 if (end == s) return false;
00333 }
00334 *buf = end;
00335 return true;
00336 }
00337
00338
00339 char *ParseWord(char **buf)
00340 {
00341 char *s = *buf, *r;
00342
00343 while (*s == ' ' || *s == '\t') s++;
00344 if (*s == '\0') return NULL;
00345
00346 if (*s == '"') {
00347 r = ++s;
00348
00349 for (;;) {
00350 if (*s == '\0') break;
00351 if (*s == '"') {
00352 *s++ = '\0';
00353 break;
00354 }
00355 s++;
00356 }
00357 } else {
00358
00359 r = s;
00360 for (;;) {
00361 if (*s == '\0') break;
00362 if (*s == ' ' || *s == '\t') {
00363 *s++ = '\0';
00364 break;
00365 }
00366 s++;
00367 }
00368 }
00369 *buf = s;
00370 return r;
00371 }
00372
00373
00374 static int TranslateArgumentIdx(int arg, int offset = 0);
00375
00376 static void EmitWordList(Buffer *buffer, const char * const *words, uint nw)
00377 {
00378 buffer->AppendByte(nw);
00379 for (uint i = 0; i < nw; i++) buffer->AppendByte((uint)strlen(words[i]) + 1);
00380 for (uint i = 0; i < nw; i++) {
00381 for (uint j = 0; words[i][j] != '\0'; j++) buffer->AppendByte(words[i][j]);
00382 buffer->AppendByte(0);
00383 }
00384 }
00385
00386 void EmitPlural(Buffer *buffer, char *buf, int value)
00387 {
00388 int argidx = _cur_argidx;
00389 int offset = 0;
00390 const char *words[5];
00391 int nw = 0;
00392
00393
00394 if (!ParseRelNum(&buf, &argidx, &offset)) argidx--;
00395
00396
00397 for (nw = 0; nw < 5; nw++) {
00398 words[nw] = ParseWord(&buf);
00399 if (words[nw] == NULL) break;
00400 }
00401
00402 if (nw == 0) {
00403 strgen_fatal("%s: No plural words", _cur_ident);
00404 }
00405
00406 if (_plural_forms[_lang.plural_form].plural_count != nw) {
00407 if (_translated) {
00408 strgen_fatal("%s: Invalid number of plural forms. Expecting %d, found %d.", _cur_ident,
00409 _plural_forms[_lang.plural_form].plural_count, nw);
00410 } else {
00411 if ((_show_todo & 2) != 0) strgen_warning("'%s' is untranslated. Tweaking english string to allow compilation for plural forms", _cur_ident);
00412 if (nw > _plural_forms[_lang.plural_form].plural_count) {
00413 nw = _plural_forms[_lang.plural_form].plural_count;
00414 } else {
00415 for (; nw < _plural_forms[_lang.plural_form].plural_count; nw++) {
00416 words[nw] = words[nw - 1];
00417 }
00418 }
00419 }
00420 }
00421
00422 buffer->AppendUtf8(SCC_PLURAL_LIST);
00423 buffer->AppendByte(_lang.plural_form);
00424 buffer->AppendByte(TranslateArgumentIdx(argidx, offset));
00425 EmitWordList(buffer, words, nw);
00426 }
00427
00428
00429 void EmitGender(Buffer *buffer, char *buf, int value)
00430 {
00431 int argidx = _cur_argidx;
00432 int offset = 0;
00433 uint nw;
00434
00435 if (buf[0] == '=') {
00436 buf++;
00437
00438
00439 nw = _lang.GetGenderIndex(buf);
00440 if (nw >= MAX_NUM_GENDERS) strgen_fatal("G argument '%s' invalid", buf);
00441
00442
00443 buffer->AppendUtf8(SCC_GENDER_INDEX);
00444 buffer->AppendByte(nw);
00445 } else {
00446 const char *words[MAX_NUM_GENDERS];
00447
00448
00449
00450 if (!ParseRelNum(&buf, &argidx, &offset)) {}
00451
00452 const CmdStruct *cmd = _cur_pcs.cmd[argidx];
00453 if (cmd == NULL || (cmd->flags & C_GENDER) == 0) {
00454 strgen_fatal("Command '%s' can't have a gender", cmd == NULL ? "<empty>" : cmd->cmd);
00455 }
00456
00457 for (nw = 0; nw < MAX_NUM_GENDERS; nw++) {
00458 words[nw] = ParseWord(&buf);
00459 if (words[nw] == NULL) break;
00460 }
00461 if (nw != _lang.num_genders) strgen_fatal("Bad # of arguments for gender command");
00462
00463 assert(IsInsideBS(cmd->value, SCC_CONTROL_START, UINT8_MAX));
00464 buffer->AppendUtf8(SCC_GENDER_LIST);
00465 buffer->AppendByte(TranslateArgumentIdx(argidx, offset));
00466 EmitWordList(buffer, words, nw);
00467 }
00468 }
00469
00470 static const CmdStruct *FindCmd(const char *s, int len)
00471 {
00472 for (const CmdStruct *cs = _cmd_structs; cs != endof(_cmd_structs); cs++) {
00473 if (strncmp(cs->cmd, s, len) == 0 && cs->cmd[len] == '\0') return cs;
00474 }
00475 return NULL;
00476 }
00477
00478 static uint ResolveCaseName(const char *str, size_t len)
00479 {
00480
00481 char case_str[CASE_GENDER_LEN];
00482 len = min(lengthof(case_str) - 1, len);
00483 memcpy(case_str, str, len);
00484 case_str[len] = '\0';
00485
00486 uint8 case_idx = _lang.GetCaseIndex(case_str);
00487 if (case_idx >= MAX_NUM_CASES) strgen_fatal("Invalid case-name '%s'", case_str);
00488 return case_idx + 1;
00489 }
00490
00491
00492
00493
00494 static const CmdStruct *ParseCommandString(const char **str, char *param, int *argno, int *casei)
00495 {
00496 const char *s = *str, *start;
00497 char c;
00498
00499 *argno = -1;
00500 *casei = -1;
00501
00502
00503 for (; *s != '{'; s++) {
00504 if (*s == '\0') return NULL;
00505 }
00506 s++;
00507
00508 if (*s >= '0' && *s <= '9') {
00509 char *end;
00510
00511 *argno = strtoul(s, &end, 0);
00512 if (*end != ':') strgen_fatal("missing arg #");
00513 s = end + 1;
00514 }
00515
00516
00517 start = s;
00518 do {
00519 c = *s++;
00520 } while (c != '}' && c != ' ' && c != '=' && c != '.' && c != 0);
00521
00522 const CmdStruct *cmd = FindCmd(start, s - start - 1);
00523 if (cmd == NULL) {
00524 strgen_error("Undefined command '%.*s'", (int)(s - start - 1), start);
00525 return NULL;
00526 }
00527
00528 if (c == '.') {
00529 const char *casep = s;
00530
00531 if (!(cmd->flags & C_CASE)) {
00532 strgen_fatal("Command '%s' can't have a case", cmd->cmd);
00533 }
00534
00535 do {
00536 c = *s++;
00537 } while (c != '}' && c != ' ' && c != '\0');
00538 *casei = ResolveCaseName(casep, s - casep - 1);
00539 }
00540
00541 if (c == '\0') {
00542 strgen_error("Missing } from command '%s'", start);
00543 return NULL;
00544 }
00545
00546
00547 if (c != '}') {
00548 if (c == '=') s--;
00549
00550 start = s;
00551 for (;;) {
00552 c = *s++;
00553 if (c == '}') break;
00554 if (c == '\0') {
00555 strgen_error("Missing } from command '%s'", start);
00556 return NULL;
00557 }
00558 if (s - start == MAX_COMMAND_PARAM_SIZE) error("param command too long");
00559 *param++ = c;
00560 }
00561 }
00562 *param = '\0';
00563
00564 *str = s;
00565
00566 return cmd;
00567 }
00568
00576 StringReader::StringReader(StringData &data, const char *file, bool master, bool translation) :
00577 data(data), file(strdup(file)), master(master), translation(translation)
00578 {
00579 }
00580
00582 StringReader::~StringReader()
00583 {
00584 free(file);
00585 }
00586
00587 static void ExtractCommandString(ParsedCommandStruct *p, const char *s, bool warnings)
00588 {
00589 char param[MAX_COMMAND_PARAM_SIZE];
00590 int argno;
00591 int argidx = 0;
00592 int casei;
00593
00594 memset(p, 0, sizeof(*p));
00595
00596 for (;;) {
00597
00598 const CmdStruct *ar = ParseCommandString(&s, param, &argno, &casei);
00599
00600 if (ar == NULL) break;
00601
00602
00603 if (argno != -1 && ar->consumes == 0) strgen_fatal("Non consumer param can't have a paramindex");
00604
00605 if (ar->consumes) {
00606 if (argno != -1) argidx = argno;
00607 if (argidx < 0 || (uint)argidx >= lengthof(p->cmd)) strgen_fatal("invalid param idx %d", argidx);
00608 if (p->cmd[argidx] != NULL && p->cmd[argidx] != ar) strgen_fatal("duplicate param idx %d", argidx);
00609
00610 p->cmd[argidx++] = ar;
00611 } else if (!(ar->flags & C_DONTCOUNT)) {
00612 if (p->np >= lengthof(p->pairs)) strgen_fatal("too many commands in string, max " PRINTF_SIZE, lengthof(p->pairs));
00613 p->pairs[p->np].a = ar;
00614 p->pairs[p->np].v = param[0] != '\0' ? strdup(param) : "";
00615 p->np++;
00616 }
00617 }
00618 }
00619
00620
00621 static const CmdStruct *TranslateCmdForCompare(const CmdStruct *a)
00622 {
00623 if (a == NULL) return NULL;
00624
00625 if (strcmp(a->cmd, "STRING1") == 0 ||
00626 strcmp(a->cmd, "STRING2") == 0 ||
00627 strcmp(a->cmd, "STRING3") == 0 ||
00628 strcmp(a->cmd, "STRING4") == 0 ||
00629 strcmp(a->cmd, "STRING5") == 0 ||
00630 strcmp(a->cmd, "STRING6") == 0 ||
00631 strcmp(a->cmd, "STRING7") == 0 ||
00632 strcmp(a->cmd, "RAW_STRING") == 0) {
00633 return FindCmd("STRING", 6);
00634 }
00635
00636 return a;
00637 }
00638
00639
00640 static bool CheckCommandsMatch(char *a, char *b, const char *name)
00641 {
00642
00643
00644
00645
00646 if (!_translation) return true;
00647
00648 ParsedCommandStruct templ;
00649 ParsedCommandStruct lang;
00650 bool result = true;
00651
00652 ExtractCommandString(&templ, b, true);
00653 ExtractCommandString(&lang, a, true);
00654
00655
00656 if (templ.np != lang.np) {
00657 strgen_warning("%s: template string and language string have a different # of commands", name);
00658 result = false;
00659 }
00660
00661 for (uint i = 0; i < templ.np; i++) {
00662
00663 bool found = false;
00664 for (uint j = 0; j < lang.np; j++) {
00665 if (templ.pairs[i].a == lang.pairs[j].a &&
00666 strcmp(templ.pairs[i].v, lang.pairs[j].v) == 0) {
00667
00668 lang.pairs[j].a = NULL;
00669 found = true;
00670 break;
00671 }
00672 }
00673
00674 if (!found) {
00675 strgen_warning("%s: command '%s' exists in template file but not in language file", name, templ.pairs[i].a->cmd);
00676 result = false;
00677 }
00678 }
00679
00680
00681
00682 for (uint i = 0; i < lengthof(templ.cmd); i++) {
00683 if (TranslateCmdForCompare(templ.cmd[i]) != lang.cmd[i]) {
00684 strgen_warning("%s: Param idx #%d '%s' doesn't match with template command '%s'", name, i,
00685 lang.cmd[i] == NULL ? "<empty>" : TranslateCmdForCompare(lang.cmd[i])->cmd,
00686 templ.cmd[i] == NULL ? "<empty>" : templ.cmd[i]->cmd);
00687 result = false;
00688 }
00689 }
00690
00691 return result;
00692 }
00693
00694 void StringReader::HandleString(char *str)
00695 {
00696 if (*str == '#') {
00697 if (str[1] == '#' && str[2] != '#') this->HandlePragma(str + 2);
00698 return;
00699 }
00700
00701
00702 if (*str == ';' || *str == ' ' || *str == '\0') return;
00703
00704 char *s = strchr(str, ':');
00705 if (s == NULL) {
00706 strgen_error("Line has no ':' delimiter");
00707 return;
00708 }
00709
00710 char *t;
00711
00712
00713 for (t = s; t > str && (t[-1] == ' ' || t[-1] == '\t'); t--) {}
00714 *t = 0;
00715 s++;
00716
00717
00718 const char *tmp;
00719 for (tmp = s; *tmp != '\0';) {
00720 size_t len = Utf8Validate(tmp);
00721 if (len == 0) strgen_fatal("Invalid UTF-8 sequence in '%s'", s);
00722
00723 WChar c;
00724 Utf8Decode(&c, tmp);
00725 if (c <= 0x001F ||
00726 c == 0x200B ||
00727 (c >= 0xE000 && c <= 0xF8FF) ||
00728 (c >= 0xFFF0 && c <= 0xFFFF)) {
00729 strgen_fatal("Unwanted UTF-8 character U+%04X in sequence '%s'", c, s);
00730 }
00731
00732 tmp += len;
00733 }
00734
00735
00736
00737 char *casep = strchr(str, '.');
00738 if (casep != NULL) *casep++ = '\0';
00739
00740
00741 LangString *ent = this->data.Find(str);
00742
00743 if (this->master) {
00744 if (casep != NULL) {
00745 strgen_error("Cases in the base translation are not supported.");
00746 return;
00747 }
00748
00749 if (ent != NULL) {
00750 strgen_error("String name '%s' is used multiple times", str);
00751 return;
00752 }
00753
00754 if (this->data.strings[this->data.next_string_id] != NULL) {
00755 strgen_error("String ID 0x%X for '%s' already in use by '%s'", this->data.next_string_id, str, this->data.strings[this->data.next_string_id]->name);
00756 return;
00757 }
00758
00759
00760 this->data.Add(str, new LangString(str, s, this->data.next_string_id++, _cur_line));
00761 } else {
00762 if (ent == NULL) {
00763 strgen_warning("String name '%s' does not exist in master file", str);
00764 return;
00765 }
00766
00767 if (ent->translated && casep == NULL) {
00768 strgen_error("String name '%s' is used multiple times", str);
00769 return;
00770 }
00771
00772
00773 if (!CheckCommandsMatch(s, ent->english, str)) return;
00774
00775 if (casep != NULL) {
00776 ent->translated_case = new Case(ResolveCaseName(casep, strlen(casep)), s, ent->translated_case);
00777 } else {
00778 ent->translated = strdup(s);
00779
00780
00781
00782 ent->line = _cur_line;
00783 }
00784 }
00785 }
00786
00787 void StringReader::HandlePragma(char *str)
00788 {
00789 if (!memcmp(str, "plural ", 7)) {
00790 _lang.plural_form = atoi(str + 7);
00791 if (_lang.plural_form >= lengthof(_plural_forms)) {
00792 strgen_fatal("Invalid pluralform %d", _lang.plural_form);
00793 }
00794 } else {
00795 strgen_fatal("unknown pragma '%s'", str);
00796 }
00797 }
00798
00799 static void rstrip(char *buf)
00800 {
00801 size_t i = strlen(buf);
00802 while (i > 0 && (buf[i - 1] == '\r' || buf[i - 1] == '\n' || buf[i - 1] == ' ')) i--;
00803 buf[i] = '\0';
00804 }
00805
00806 void StringReader::ParseFile()
00807 {
00808 char buf[2048];
00809 _warnings = _errors = 0;
00810
00811 _translation = this->master || this->translation;
00812 _file = this->file;
00813
00814
00815 MemSetT(&_lang, 0);
00816 strecpy(_lang.digit_group_separator, ",", lastof(_lang.digit_group_separator));
00817 strecpy(_lang.digit_group_separator_currency, ",", lastof(_lang.digit_group_separator_currency));
00818 strecpy(_lang.digit_decimal_separator, ".", lastof(_lang.digit_decimal_separator));
00819
00820 _cur_line = 1;
00821 while (this->ReadLine(buf, sizeof(buf)) != NULL) {
00822 rstrip(buf);
00823 this->HandleString(buf);
00824 _cur_line++;
00825 }
00826 }
00827
00832 void HeaderWriter::WriteHeader(const StringData &data)
00833 {
00834 int last = 0;
00835 for (size_t i = 0; i < data.max_strings; i++) {
00836 if (data.strings[i] != NULL) {
00837 this->WriteStringID(data.strings[i]->name, (int)i);
00838 last = (int)i;
00839 }
00840 }
00841
00842 this->WriteStringID("STR_LAST_STRINGID", last);
00843 }
00844
00845 static int TranslateArgumentIdx(int argidx, int offset)
00846 {
00847 int sum;
00848
00849 if (argidx < 0 || (uint)argidx >= lengthof(_cur_pcs.cmd)) {
00850 strgen_fatal("invalid argidx %d", argidx);
00851 }
00852 const CmdStruct *cs = _cur_pcs.cmd[argidx];
00853 if (cs != NULL && cs->consumes <= offset) {
00854 strgen_fatal("invalid argidx offset %d:%d", argidx, offset);
00855 }
00856
00857 if (_cur_pcs.cmd[argidx] == NULL) {
00858 strgen_fatal("no command for this argidx %d", argidx);
00859 }
00860
00861 for (int i = sum = 0; i < argidx; i++) {
00862 const CmdStruct *cs = _cur_pcs.cmd[i];
00863
00864 sum += (cs != NULL) ? cs->consumes : 1;
00865 }
00866
00867 return sum + offset;
00868 }
00869
00870 static void PutArgidxCommand(Buffer *buffer)
00871 {
00872 buffer->AppendUtf8(SCC_ARG_INDEX);
00873 buffer->AppendByte(TranslateArgumentIdx(_cur_argidx));
00874 }
00875
00876
00877 static void PutCommandString(Buffer *buffer, const char *str)
00878 {
00879 _cur_argidx = 0;
00880
00881 while (*str != '\0') {
00882
00883 if (*str != '{') {
00884 buffer->AppendByte(*str++);
00885 continue;
00886 }
00887
00888 char param[MAX_COMMAND_PARAM_SIZE];
00889 int argno;
00890 int casei;
00891 const CmdStruct *cs = ParseCommandString(&str, param, &argno, &casei);
00892 if (cs == NULL) break;
00893
00894 if (casei != -1) {
00895 buffer->AppendUtf8(SCC_SET_CASE);
00896 buffer->AppendByte(casei);
00897 }
00898
00899
00900 if (cs->consumes > 0) {
00901
00902 if (argno != -1 && argno != _cur_argidx) {
00903 _cur_argidx = argno;
00904 PutArgidxCommand(buffer);
00905 }
00906
00907
00908 cs = _cur_pcs.cmd[_cur_argidx++];
00909 if (cs == NULL) {
00910 strgen_fatal("%s: No argument exists at position %d", _cur_ident, _cur_argidx - 1);
00911 }
00912 }
00913
00914 cs->proc(buffer, param, cs->value);
00915 }
00916 }
00917
00922 void LanguageWriter::WriteLength(uint length)
00923 {
00924 char buffer[2];
00925 int offs = 0;
00926 if (length >= 0x4000) {
00927 strgen_fatal("string too long");
00928 }
00929
00930 if (length >= 0xC0) {
00931 buffer[offs++] = (length >> 8) | 0xC0;
00932 }
00933 buffer[offs++] = length & 0xFF;
00934 this->Write((byte*)buffer, offs);
00935 }
00936
00941 void LanguageWriter::WriteLang(const StringData &data)
00942 {
00943 uint *in_use = AllocaM(uint, data.tabs);
00944 for (size_t tab = 0; tab < data.tabs; tab++) {
00945 uint n = data.CountInUse((uint)tab);
00946
00947 in_use[tab] = n;
00948 _lang.offsets[tab] = TO_LE16(n);
00949
00950 for (uint j = 0; j != in_use[tab]; j++) {
00951 const LangString *ls = data.strings[(tab * TAB_SIZE) + j];
00952 if (ls != NULL && ls->translated == NULL) _lang.missing++;
00953 }
00954 }
00955
00956 _lang.ident = TO_LE32(LanguagePackHeader::IDENT);
00957 _lang.version = TO_LE32(data.Version());
00958 _lang.missing = TO_LE16(_lang.missing);
00959 _lang.winlangid = TO_LE16(_lang.winlangid);
00960
00961 this->WriteHeader(&_lang);
00962 Buffer buffer;
00963
00964 for (size_t tab = 0; tab < data.tabs; tab++) {
00965 for (uint j = 0; j != in_use[tab]; j++) {
00966 const LangString *ls = data.strings[(tab * TAB_SIZE) + j];
00967 const Case *casep;
00968 const char *cmdp;
00969
00970
00971 if (ls == NULL) {
00972 this->WriteLength(0);
00973 continue;
00974 }
00975
00976 _cur_ident = ls->name;
00977 _cur_line = ls->line;
00978
00979
00980 if (_show_todo > 0 && ls->translated == NULL) {
00981 if ((_show_todo & 2) != 0) {
00982 strgen_warning("'%s' is untranslated", ls->name);
00983 }
00984 if ((_show_todo & 1) != 0) {
00985 const char *s = "<TODO> ";
00986 while (*s != '\0') buffer.AppendByte(*s++);
00987 }
00988 }
00989
00990
00991 ExtractCommandString(&_cur_pcs, ls->english, false);
00992
00993 if (ls->translated_case != NULL || ls->translated != NULL) {
00994 casep = ls->translated_case;
00995 cmdp = ls->translated;
00996 } else {
00997 casep = NULL;
00998 cmdp = ls->english;
00999 }
01000
01001 _translated = cmdp != ls->english;
01002
01003 if (casep != NULL) {
01004 const Case *c;
01005 uint num;
01006
01007
01008
01009
01010
01011 buffer.AppendUtf8(SCC_SWITCH_CASE);
01012
01013 for (num = 0, c = casep; c; c = c->next) num++;
01014 buffer.AppendByte(num);
01015
01016
01017 for (c = casep; c != NULL; c = c->next) {
01018 buffer.AppendByte(c->caseidx);
01019
01020 uint pos = buffer.Length();
01021 buffer.AppendByte(0);
01022 buffer.AppendByte(0);
01023
01024 PutCommandString(&buffer, c->string);
01025 buffer.AppendByte(0);
01026
01027 uint size = buffer.Length() - (pos + 2);
01028 buffer[pos + 0] = GB(size, 8, 8);
01029 buffer[pos + 1] = GB(size, 0, 8);
01030 }
01031 }
01032
01033 if (cmdp != NULL) PutCommandString(&buffer, cmdp);
01034
01035 this->WriteLength(buffer.Length());
01036 this->Write(buffer.Begin(), buffer.Length());
01037 buffer.Clear();
01038 }
01039 }
01040 }