From 08b5b09fd39c6bba9a4e8c7ba2fea2fbabae7520 Mon Sep 17 00:00:00 2001
From: Spoike <acceptthis@users.sourceforge.net>
Date: Thu, 27 May 2021 11:34:22 +0000
Subject: [PATCH] Add -Fdumplocalisation arg to fteqcc to dump localisation
 stuff (in part to try to encourage more people to use this stuff).

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5873 fc73d0e0-1445-4013-8a0c-d673dee63da5
---
 engine/qclib/qcc.h         |   1 +
 engine/qclib/qcc_pr_comp.c |  11 ++
 engine/qclib/qcc_pr_lex.c  | 211 +++++++++++++++++++------------------
 engine/qclib/qccmain.c     | 115 ++++++++++++++++++++
 4 files changed, 237 insertions(+), 101 deletions(-)

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)
 	{