diff --git a/engine/qclib/qcc.h b/engine/qclib/qcc.h index 1b16aeaaf..11c1a517c 100644 --- a/engine/qclib/qcc.h +++ b/engine/qclib/qcc.h @@ -582,6 +582,7 @@ tt_punct, // code punctuation tt_immediate, // string, float, vector } token_type_t; +extern char *pr_token_precomment; extern char pr_token[8192]; extern token_type_t pr_token_type; extern int pr_token_line; diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index 4cc660f30..f6268ee9b 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -7450,18 +7450,26 @@ static QCC_sref_t QCC_PR_ParseFunctionCall (QCC_ref_t *funcref) //warning, the f } if (!strcmp(funcname, "_")) { + char *comment = pr_token_precomment; func.sym->unused = true; func.sym->referenced = true; QCC_FreeTemp(func); if (pr_token_type == tt_immediate && pr_immediate_type->type == ev_string) { d = QCC_MakeTranslateStringConst(pr_immediate_string); + + d.sym->comment = comment; QCC_PR_Lex(); + if (!d.sym->comment) + d.sym->comment = pr_token_precomment; + if (QCC_PR_CheckTokenComment (")", &d.sym->comment)) + return d; } else { QCC_PR_ParseErrorPrintSRef (ERR_TYPEMISMATCHPARM, func, "_() intrinsic accepts only a string immediate", 1); d = nullsref; + } QCC_PR_Expect(")"); return d; @@ -8370,6 +8378,9 @@ static QCC_sref_t QCC_MakeStringConstInternal(const char *value, size_t length, cn->arraysize = 0; cn->localscope = false; + cn->filen = s_filen; + cn->s_line = pr_source_line; + // copy the immediate to the global area cn->ofs = 0; cn->symbolheader = cn; diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index 6e7cc10e9..5c45e45ee 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -14,6 +14,7 @@ CompilerConstant_t *QCC_PR_CheckCompConstDefined(const char *def); int QCC_PR_CheckCompConst(void); pbool QCC_Include(const char *filename); void QCC_FreeDef(QCC_def_t *def); +void QCC_PR_LexComment(char **comment); extern pbool destfile_explicit; extern char destfile[1024]; @@ -36,6 +37,7 @@ char *pr_line_start; // start of current source line int pr_bracelevel; +char *pr_token_precomment; char pr_token[8192]; token_type_t pr_token_type; int pr_token_line; @@ -1641,7 +1643,7 @@ void QCC_PR_LexString (void) raw = 0; texttype = 0; - QCC_PR_LexWhitespace(false); + QCC_PR_LexComment(&pr_token_precomment); if (flag_qccx && *pr_file_p == ':') { @@ -3819,7 +3821,9 @@ void QCC_PR_Lex (void) return; } - QCC_PR_LexWhitespace (false); + pr_token_precomment = NULL; + QCC_PR_LexComment(&pr_token_precomment); +// QCC_PR_LexWhitespace (false); pr_token_line_last = pr_token_line; pr_token_line = pr_source_line; @@ -4214,7 +4218,7 @@ void QCC_PR_Expect (const char *string) } #endif -pbool QCC_PR_CheckTokenComment(const char *string, char **comment) +void QCC_PR_LexComment(char **comment) { char c; char *start; @@ -4224,6 +4228,108 @@ pbool QCC_PR_CheckTokenComment(const char *string, char **comment) pbool replace = true; pbool nextcomment = true; + // skip whitespace + nl = false; + while(nextcomment) + { + nextcomment = false; + + while ((c = *pr_file_p) && qcc_iswhite(c)) + { + if (qcc_islineending(c, pr_file_p[1])) //allow new lines, but only if there's whitespace before any tokens, and no double newlines. + { + if (nl) + { + pr_file_p++; + QCC_PR_NewLine(false); + break; + } + nl = true; + } + else + { + pr_file_p++; + nl = false; + } + } + if (nl) + break; + + // parse // comments + if (c=='/' && pr_file_p[1] == '/') + { + pr_file_p += 2; + while (*pr_file_p == ' ' || *pr_file_p == '\t') + pr_file_p++; + start = pr_file_p; + while (*pr_file_p && *pr_file_p != '\n') + pr_file_p++; + + if (*pr_file_p == '\n') + { + pr_file_p++; + QCC_PR_NewLine(false); + } + + old = replace?NULL:*comment; + replace = false; + oldlen = old?strlen(old)+1:0; + *comment = qccHunkAlloc(oldlen + (pr_file_p-start)+1); + if (oldlen) + { + memcpy(*comment, old, oldlen-1); + memcpy(*comment+oldlen-1, "\n", 1); + } + memcpy(*comment + oldlen, start, pr_file_p - start); + oldlen = oldlen+pr_file_p - start; + while(oldlen > 0 && ((*comment)[oldlen-1] == '\r' || (*comment)[oldlen-1] == '\n' || (*comment)[oldlen-1] == '\t' || (*comment)[oldlen-1] == ' ')) + oldlen--; + (*comment)[oldlen] = 0; + nextcomment = true; //include the next // too + nl = true; + } + // parse /* comments + else if (c=='/' && pr_file_p[1] == '*' && replace) + { + pr_file_p+=1; + start = pr_file_p+1; + + do + { + pr_file_p++; + if (pr_file_p[0]=='\n') + { + QCC_PR_NewLine(true); + } + else if (pr_file_p[1] == 0) + { + QCC_PR_ParseError(0, "EOF inside comment\n"); + break; + } + if (pr_file_p[0] == '/' && pr_file_p[1] == '*') + QCC_PR_ParseWarning(WARN_NESTEDCOMMENT, "\"/*\" inside comment"); + } while (pr_file_p[0] != '*' || pr_file_p[1] != '/'); + + if (pr_file_p[1] == 0) + break; + + old = replace?NULL:*comment; + replace = false; + oldlen = old?strlen(old):0; + *comment = qccHunkAlloc(oldlen + (pr_file_p-start)+1); + memcpy(*comment, old, oldlen); + memcpy(*comment + oldlen, start, pr_file_p - start); + (*comment)[oldlen+pr_file_p - start] = 0; + + pr_file_p+=2; + } + } + + QCC_PR_LexWhitespace(false); +} + +pbool QCC_PR_CheckTokenComment(const char *string, char **comment) +{ if (pr_token_type != tt_punct) return false; @@ -4231,104 +4337,7 @@ pbool QCC_PR_CheckTokenComment(const char *string, char **comment) return false; if (comment) - { - // skip whitespace - nl = false; - while(nextcomment) - { - nextcomment = false; - - while ((c = *pr_file_p) && qcc_iswhite(c)) - { - if (c=='\n') //allow new lines, but only if there's whitespace before any tokens, and no double newlines. - { - if (nl) - { - pr_file_p++; - QCC_PR_NewLine(false); - break; - } - nl = true; - } - else - { - pr_file_p++; - nl = false; - } - } - if (nl) - break; - - // parse // comments - if (c=='/' && pr_file_p[1] == '/') - { - pr_file_p += 2; - while (*pr_file_p == ' ' || *pr_file_p == '\t') - pr_file_p++; - start = pr_file_p; - while (*pr_file_p && *pr_file_p != '\n') - pr_file_p++; - - if (*pr_file_p == '\n') - { - pr_file_p++; - QCC_PR_NewLine(false); - } - - old = replace?NULL:*comment; - replace = false; - oldlen = old?strlen(old)+1:0; - *comment = qccHunkAlloc(oldlen + (pr_file_p-start)+1); - if (oldlen) - { - memcpy(*comment, old, oldlen-1); - memcpy(*comment+oldlen-1, "\n", 1); - } - memcpy(*comment + oldlen, start, pr_file_p - start); - oldlen = oldlen+pr_file_p - start; - while(oldlen > 0 && ((*comment)[oldlen-1] == '\r' || (*comment)[oldlen-1] == '\n' || (*comment)[oldlen-1] == '\t' || (*comment)[oldlen-1] == ' ')) - oldlen--; - (*comment)[oldlen] = 0; - nextcomment = true; //include the next // too - nl = true; - } - // parse /* comments - else if (c=='/' && pr_file_p[1] == '*' && replace) - { - pr_file_p+=1; - start = pr_file_p+1; - - do - { - pr_file_p++; - if (pr_file_p[0]=='\n') - { - QCC_PR_NewLine(true); - } - else if (pr_file_p[1] == 0) - { - QCC_PR_ParseError(0, "EOF inside comment\n"); - break; - } - if (pr_file_p[0] == '/' && pr_file_p[1] == '*') - QCC_PR_ParseWarning(WARN_NESTEDCOMMENT, "\"/*\" inside comment"); - } while (pr_file_p[0] != '*' || pr_file_p[1] != '/'); - - if (pr_file_p[1] == 0) - break; - - old = replace?NULL:*comment; - replace = false; - oldlen = old?strlen(old):0; - *comment = qccHunkAlloc(oldlen + (pr_file_p-start)+1); - memcpy(*comment, old, oldlen); - memcpy(*comment + oldlen, start, pr_file_p - start); - (*comment)[oldlen+pr_file_p - start] = 0; - - pr_file_p+=2; - } - } - } + QCC_PR_LexComment(comment); //and then do the rest properly. QCC_PR_Lex (); diff --git a/engine/qclib/qccmain.c b/engine/qclib/qccmain.c index 853a4f51c..44510f605 100644 --- a/engine/qclib/qccmain.c +++ b/engine/qclib/qccmain.c @@ -142,6 +142,7 @@ static pbool flag_dumpfilenames; static pbool flag_dumpfields; static pbool flag_dumpsymbols; static pbool flag_dumpautocvars; +static pbool flag_dumplocalisation; struct { @@ -424,6 +425,7 @@ compiler_flag_t compiler_flag[] = { {&flag_dumpfields, FLAG_MIDCOMPILE,"dumpfields", "Write a .fld file", "Writes a .fld file that shows which fields are defined, along with their offsets etc, for weird debugging."}, {&flag_dumpsymbols, FLAG_MIDCOMPILE,"dumpsymbols", "Write a .sym file", "Writes a .sym file alongside the dat which contains a list of all global symbols defined in the code (before stripping)"}, {&flag_dumpautocvars, FLAG_MIDCOMPILE,"dumpautocvars","Write a .cfg file", "Writes a .cfg file that contains a default value for each autocvar listed in the code"}, + {&flag_dumplocalisation,FLAG_MIDCOMPILE,"dumplocalisation","Write a .pot file", "Writes a .po template file from your _("") strings that can be edited (with eg gettext's tools) for translations, resulting in eg csprogs.en_US.po vs csprogs.en.po and other various other dialects vs languages."}, {NULL} }; @@ -724,6 +726,9 @@ static void QCC_DumpAutoCvars (const char *outputname) QCC_def_t *def = QCC_PR_GetDef(NULL, n, NULL, false, 0, 0); n += 9; + if (!def) + continue; //erk? + if (def->comment) desc = def->comment; else @@ -765,6 +770,114 @@ static void QCC_DumpAutoCvars (const char *outputname) } } +static void QCC_DumpLocalisation (const char *outputname) +{ + char line[65536]; + int h, o; + QCC_def_t *def; + char *n; + + snprintf(line, sizeof(line), "%s.pot", outputname); + h = SafeOpenWrite (line, 2*1024*1024); + if (h >= 0) + { + for (def = pr.def_head.next ; def ; def = def->next) + { + if (!strncmp(def->name, "dotranslate_", 12)) + { + const QCC_eval_t *val = (const QCC_eval_t*)def->symboldata; + if (def->type->type != ev_string) + continue; + + if (def->comment) + { + n = def->comment; + snprintf(line, sizeof(line), "#. "); + for (o = strlen(line); *n && o < countof(line)-10; n++) + { + if (*n == '\n') + { + if (n[1]) + { + line[o++] = '\n'; + line[o++] = '#'; + line[o++] = '.'; + line[o++] = ' '; + continue; + } + else line[++o] = 'n'; + } + else if (*n == '\\') line[++o] = '\\'; + else if (*n == '\"') line[++o] = '\"'; + else if (*n == '\n') line[++o] = 'n'; + else if (*n == '\r') line[++o] = 'r'; + else if (*n == '\t') line[++o] = 't'; + else + { //hopefully the programmer used utf-8... + line[o++] = *n; + continue; + } + line[o++-1] = '\\'; + } + line[o++] = '\n'; + line[o++] = 0; + SafeWrite(h, line, strlen(line)); + } + + if (def->filen) + { //strip any extra macro info there... + char *c = strchr(def->filen, ':'); + if (c && (c[1] < '0' || c[1] > '9')) //don't get fooled by windows paths... + c = strchr(c+1, ':'); + if (c) + { + *c = 0; + snprintf(line, sizeof(line), "#: %s:%i\n", def->filen, def->s_line); + *c = ':'; + } + else + snprintf(line, sizeof(line), "#: %s:%i\n", def->filen, def->s_line); + SafeWrite(h, line, strlen(line)); + } + + n = strings + val->_int; + snprintf(line, sizeof(line), "msgid \""); + for (o = strlen(line); *n && o < countof(line)-5; n++) + { + if (*n == '\n' && n[1] && o < countof(line)-10) + { //split multi-line stuff onto multiple lines, becase we can. + line[o++] = '\\'; + line[o++] = 'n'; + line[o++] = '\"'; + line[o++] = '\n'; + line[o++] = '\"'; + continue; + } + else if (*n == '\\') line[++o] = '\\'; + else if (*n == '\"') line[++o] = '\"'; + else if (*n == '\n') line[++o] = 'n'; + else if (*n == '\r') line[++o] = 'r'; + else if (*n == '\t') line[++o] = 't'; + else + { //hopefully the programmer used utf-8... + line[o++] = *n; + continue; + } + line[o++-1] = '\\'; + } + line[o++] = '\"'; + line[o++] = '\n'; + line[o++] = 0; + SafeWrite(h, line, strlen(line)); + + snprintf(line, sizeof(line), "msgstr \"\"\n\n"); + SafeWrite(h, line, strlen(line)); + } + } + } + SafeClose(h); +} + static void QCC_DumpFiles (const char *outputname) { struct @@ -2285,6 +2398,8 @@ strofs = (strofs+3)&~3; QCC_DumpSymbols(destfile); if (flag_dumpautocvars) QCC_DumpAutoCvars(destfile); + if (flag_dumplocalisation) + QCC_DumpLocalisation(destfile); switch(outputsttype) {