From 24b1c046d0ecbc26851ead83052531a4dda86e92 Mon Sep 17 00:00:00 2001
From: vortex <vortex@d7cf8633-e32d-0410-b094-e92efae38249>
Date: Tue, 20 Mar 2012 21:55:04 +0000
Subject: [PATCH] Work-in-progress extension: DP_QC_STRINGBUFFERS_EXT_WIP.

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@11767 d7cf8633-e32d-0410-b094-e92efae38249
::stable-branch::merge=adaa4ba5564a89300b5e8998d0ce95f75348135c
---
 clvm_cmds.c            |   8 +-
 dpdefs/dpextensions.qc |  22 +++
 mvm_cmds.c             |   9 +-
 progsvm.h              |   1 -
 prvm_cmds.c            | 349 ++++++++++++++++++++++++++++++++++++++++-
 prvm_cmds.h            |   5 +
 svvm_cmds.c            |   9 +-
 7 files changed, 389 insertions(+), 14 deletions(-)

diff --git a/clvm_cmds.c b/clvm_cmds.c
index cdb8f427..b1ed1035 100644
--- a/clvm_cmds.c
+++ b/clvm_cmds.c
@@ -4742,10 +4742,10 @@ VM_CL_setpause,					// #531 float(float ispaused) setpause = #531 (DP_CSQC_SETPA
 VM_log,							// #532
 VM_getsoundtime,				// #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME)
 VM_soundlength,					// #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME)
-NULL,							// #535
-NULL,							// #536
-NULL,							// #537
-NULL,							// #538
+VM_buf_loadfile,                // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_buf_writefile,               // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_bufstr_find,                 // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_matchpattern,                // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP)
 NULL,							// #539
 VM_physics_enable,				// #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE)
 VM_physics_addforce,			// #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE)
diff --git a/dpdefs/dpextensions.qc b/dpdefs/dpextensions.qc
index 0acf262b..96f98350 100644
--- a/dpdefs/dpextensions.qc
+++ b/dpdefs/dpextensions.qc
@@ -2485,6 +2485,28 @@ void(float bufhandle, float string_index) bufstr_free = #469;
 //cvars that start with pattern but not with antipattern will be stored into the buffer
 void(float bufhandle, string pattern, string antipattern) buf_cvarlist = #517;
 
+//DP_QC_STRINGBUFFERS_EXT_WIP
+//idea: VorteX
+//darkplaces implementation: VorteX
+//constant definitions:
+const float MATCH_AUTO = 0;
+const float MATCH_WHOLE = 1;
+const float MATCH_LEFT = 2;
+const float MATCH_RIGHT = 3;
+const float MATCH_MIDDLE = 4;
+const float MATCH_PATTERN = 5;
+//builtin definitions:
+float(string filename, float bufhandle) buf_loadfile = #535; // append each line of file as new buffer string, return 1 if succesful
+float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile = #536; // writes buffer strings as lines, returns 1 if succesful
+float(float bufhandle, string match, float matchrule, float startpos, float step) bufstr_find = #537; // returns string index
+float(string s, string pattern, float matchrule) matchpattern = #538; // returns 0/1
+float(string s, string pattern, float matchrule, float pos) matchpatternofs = #538;
+//description:
+//provides a set of functions to manipulate with string buffers
+//pattern wildcards: * - any character (or no characters), ? - any 1 character
+//Warning: This extension is work-in-progress, it may be changed/revamped/removed at any time, dont use it if you dont want any trouble
+//wip note: UTF8 is not supported yet
+
 //DP_QC_STRREPLACE
 //idea: Sajt
 //darkplaces implementation: Sajt
diff --git a/mvm_cmds.c b/mvm_cmds.c
index 79edacfb..63a798b5 100644
--- a/mvm_cmds.c
+++ b/mvm_cmds.c
@@ -36,6 +36,7 @@ const char *vm_m_extensions =
 "DP_QC_STRFTIME "
 "DP_QC_STRINGBUFFERS "
 "DP_QC_STRINGBUFFERS_CVARLIST "
+"DP_QC_STRINGBUFFERS_EXT_WIP "
 "DP_QC_STRINGCOLORFUNCTIONS "
 "DP_QC_STRING_CASE_FUNCTIONS "
 "DP_QC_STRREPLACE "
@@ -1434,10 +1435,10 @@ NULL,									// #531
 VM_log,									// #532
 VM_getsoundtime,						// #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME)
 VM_soundlength,							// #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME)
-NULL,									// #535
-NULL,									// #536
-NULL,									// #537
-NULL,									// #538
+VM_buf_loadfile,                        // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_buf_writefile,                       // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_bufstr_find,                         // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_matchpattern,                        // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP)
 NULL,									// #539
 NULL,									// #540
 NULL,									// #541
diff --git a/progsvm.h b/progsvm.h
index b87822a4..343b99bb 100644
--- a/progsvm.h
+++ b/progsvm.h
@@ -512,7 +512,6 @@ prvm_prog_funcoffsets_t;
 
 // stringbuffer flags
 #define STRINGBUFFER_SAVED	1 // saved in savegames
-
 typedef struct prvm_stringbuffer_s
 {
 	int max_strings;
diff --git a/prvm_cmds.c b/prvm_cmds.c
index c4a48668..e45c4097 100644
--- a/prvm_cmds.c
+++ b/prvm_cmds.c
@@ -4627,7 +4627,7 @@ void VM_buf_create (prvm_prog_t *prog)
 	int i;
 	
 	VM_SAFEPARMCOUNTRANGE(0, 2, VM_buf_create);
-
+	
 	// VorteX: optional parm1 (buffer format) is unfinished, to keep intact with future databuffers extension must be set to "string"
 	if(prog->argc >= 1 && strcmp(PRVM_G_STRING(OFS_PARM0), "string"))
 	{
@@ -4991,11 +4991,358 @@ void VM_bufstr_free (prvm_prog_t *prog)
 	BufStr_Shrink(prog, stringbuffer);
 }
 
+/*
+========================
+VM_buf_loadfile
+load a file into string buffer, return 0 or 1
+float buf_loadfile(string filename, float bufhandle) = #535;
+========================
+*/
+void VM_buf_loadfile(prvm_prog_t *prog)
+{
+	size_t alloclen;
+	prvm_stringbuffer_t *stringbuffer;
+	char string[VM_STRINGTEMP_LENGTH];
+	int filenum, strindex, c, end;
+	const char *filename;
+	char vabuf[1024];
 
+	VM_SAFEPARMCOUNT(2, VM_buf_loadfile);
 
+	// get file
+	filename = PRVM_G_STRING(OFS_PARM0);
+	for (filenum = 0;filenum < PRVM_MAX_OPENFILES;filenum++)
+		if (prog->openfiles[filenum] == NULL)
+			break;
+	prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "data/%s", filename), false);
+	if (prog->openfiles[filenum] == NULL)
+		prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "%s", filename), false);
+	if (prog->openfiles[filenum] == NULL)
+	{
+		if (developer_extra.integer)
+			VM_Warning(prog, "VM_buf_loadfile: failed to open file %s in %s\n", filename, prog->name, filename);
+		PRVM_G_FLOAT(OFS_RETURN) = 0;
+		return;
+	}
 
+	// get string buffer
+	stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM1));
+	if(!stringbuffer)
+	{
+		VM_Warning(prog, "VM_buf_loadfile: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name);
+		PRVM_G_FLOAT(OFS_RETURN) = 0;
+		return;
+	}
 
+	// read file (append to the end of buffer)
+	strindex = stringbuffer->num_strings;
+	while(1)
+	{
+		// read line
+		end = 0;
+		for (;;)
+		{
+			c = FS_Getc(prog->openfiles[filenum]);
+			if (c == '\r' || c == '\n' || c < 0)
+				break;
+			if (end < VM_STRINGTEMP_LENGTH - 1)
+				string[end++] = c;
+		}
+		string[end] = 0;
+		// remove \n following \r
+		if (c == '\r')
+		{
+			c = FS_Getc(prog->openfiles[filenum]);
+			if (c != '\n')
+				FS_UnGetc(prog->openfiles[filenum], (unsigned char)c);
+		}
+		// add and continue
+		if (c >= 0 || end)
+		{
+			BufStr_Expand(prog, stringbuffer, strindex);
+			stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1);
+			alloclen = strlen(string) + 1;
+			stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen);
+			memcpy(stringbuffer->strings[strindex], string, alloclen);
+			strindex = stringbuffer->num_strings;
+		}
+		else
+			break;
+	}
 
+	// close file
+	FS_Close(prog->openfiles[filenum]);
+	prog->openfiles[filenum] = NULL;
+	if (prog->openfiles_origin[filenum])
+		PRVM_Free((char *)prog->openfiles_origin[filenum]);
+	PRVM_G_FLOAT(OFS_RETURN) = 1;
+}
+
+/*
+========================
+VM_buf_writefile
+writes stringbuffer to a file, returns 0 or 1
+float buf_writefile(float filehandle, float bufhandle, [, float startpos, float numstrings]) = #468;
+========================
+*/
+
+void VM_buf_writefile(prvm_prog_t *prog)
+{
+	int filenum, strindex, strnum, strlength;
+	prvm_stringbuffer_t *stringbuffer;
+
+	VM_SAFEPARMCOUNTRANGE(2, 4, VM_buf_writefile);
+
+	// get file
+	filenum = (int)PRVM_G_FLOAT(OFS_PARM0);
+	if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES)
+	{
+		VM_Warning(prog, "VM_buf_writefile: invalid file handle %i used in %s\n", filenum, prog->name);
+		return;
+	}
+	if (prog->openfiles[filenum] == NULL)
+	{
+		VM_Warning(prog, "VM_buf_writefile: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name);
+		return;
+	}
+	
+	// get string buffer
+	stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM1));
+	if(!stringbuffer)
+	{
+		VM_Warning(prog, "VM_buf_writefile: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name);
+		PRVM_G_FLOAT(OFS_RETURN) = 0;
+		return;
+	}
+
+	// get start and end parms
+	if (prog->argc > 3)
+	{
+		strindex = (int)PRVM_G_FLOAT(OFS_PARM2);
+		strnum = (int)PRVM_G_FLOAT(OFS_PARM3);
+	}
+	else if (prog->argc > 2)
+	{
+		strindex = (int)PRVM_G_FLOAT(OFS_PARM2);
+		strnum = stringbuffer->num_strings - strindex;
+	}
+	else
+	{
+		strindex = 0;
+		strnum = stringbuffer->num_strings;
+	}
+	if (strindex < 0 || strindex >= stringbuffer->num_strings)
+	{
+		VM_Warning(prog, "VM_buf_writefile: wrong start string index %i used in %s\n", strindex, prog->name);
+		PRVM_G_FLOAT(OFS_RETURN) = 0;
+		return;
+	}
+	if (strnum < 0)
+	{
+		VM_Warning(prog, "VM_buf_writefile: wrong strings count %i used in %s\n", strnum, prog->name);
+		PRVM_G_FLOAT(OFS_RETURN) = 0;
+		return;
+	}
+
+	// write
+	while(strindex < stringbuffer->num_strings && strnum)
+	{
+		if (stringbuffer->strings[strindex])
+		{
+			if ((strlength = strlen(stringbuffer->strings[strindex])))
+				FS_Write(prog->openfiles[filenum], stringbuffer->strings[strindex], strlength);
+			FS_Write(prog->openfiles[filenum], "\n", 1);
+		}
+		strindex++;
+		strnum--;
+	}
+
+	PRVM_G_FLOAT(OFS_RETURN) = 1;
+}
+
+#define MATCH_AUTO     0
+#define MATCH_WHOLE    1
+#define MATCH_LEFT     2
+#define MATCH_RIGHT    3
+#define MATCH_MIDDLE   4
+#define MATCH_PATTERN  5
+
+const char *detect_match_rule(char *pattern, int *matchrule)
+{
+	char *ppos, *qpos;
+	int patternlength;
+
+	patternlength = strlen(pattern);
+	ppos = strchr(pattern, '*');
+	qpos = strchr(pattern, '?');
+	// has ? - pattern
+	if (qpos) 
+	{
+		*matchrule = MATCH_PATTERN;
+		return pattern;
+	}
+	// has * - left, mid, right or pattern
+	if (ppos)
+	{
+		// starts with * - may be right/mid or pattern
+		if ((ppos - pattern) == 0)
+		{
+			ppos = strchr(pattern+1, '*');
+			// *something 
+			if (!ppos) 
+			{
+				*matchrule = MATCH_RIGHT;
+				return pattern+1;
+			}
+			// *something*
+			if ((ppos - pattern) == patternlength)
+			{
+				*matchrule = MATCH_MIDDLE;
+				*ppos = 0;
+				return pattern+1;
+			}
+			// *som*thing
+			*matchrule = MATCH_PATTERN;
+			return pattern;
+		}
+		// end with * - left
+		if ((ppos - pattern) == patternlength)
+		{
+			*matchrule = MATCH_LEFT;
+			*ppos = 0;
+			return pattern;
+		}
+		// som*thing
+		*matchrule = MATCH_PATTERN;
+		return pattern;
+	}
+	// have no wildcards - whole string
+	*matchrule = MATCH_WHOLE;
+	return pattern;
+}
+
+// todo: support UTF8
+qboolean match_rule(const char *string, int max_string, const char *pattern, int patternlength, int rule)
+{
+	const char *mid;
+
+	if (rule == 1)
+		return !strncmp(string, pattern, max_string) ? true : false;
+	if (rule == 2)
+		return !strncmp(string, pattern, patternlength) ? true : false;
+	if (rule == 3)
+	{
+		mid = strstr(string, pattern);
+		return mid && !*(mid+patternlength);
+	}
+	if (rule == 4)
+		return strstr(string, pattern) ? true : false;
+	// pattern
+	return matchpattern_with_separator(string, pattern, false, "", false) ? true : false;
+}
+
+/*
+========================
+VM_bufstr_find
+find an index of bufstring matching rule
+float bufstr_find(float bufhandle, string match, float matchrule, float startpos, float step) = #468;
+========================
+*/
+
+void VM_bufstr_find(prvm_prog_t *prog)
+{
+	prvm_stringbuffer_t *stringbuffer;
+	char string[VM_STRINGTEMP_LENGTH];
+	int matchrule, matchlen, i, step;
+	const char *match;
+	
+	VM_SAFEPARMCOUNTRANGE(3, 5, VM_bufstr_find);
+
+	PRVM_G_FLOAT(OFS_RETURN) = -1;
+
+	// get string buffer
+	stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0));
+	if(!stringbuffer)
+	{
+		VM_Warning(prog, "VM_bufstr_find: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name);
+		return;
+	}
+
+	// get pattern/rule
+	matchrule = (int)PRVM_G_FLOAT(OFS_PARM2);
+	if (matchrule < 0 && matchrule > 5)
+	{
+		VM_Warning(prog, "VM_bufstr_find: invalid match rule %i in %s\n", matchrule, prog->name);
+		return;
+	}
+	if (matchrule)
+		match = PRVM_G_STRING(OFS_PARM1);
+	else
+	{
+		strlcpy(string, PRVM_G_STRING(OFS_PARM1), sizeof(string));
+		match = detect_match_rule(string, &matchrule);
+	}
+	matchlen = strlen(match);
+
+	// find
+	i = (prog->argc > 3) ? (int)PRVM_G_FLOAT(OFS_PARM3) : 0;
+	step = (prog->argc > 4) ? (int)PRVM_G_FLOAT(OFS_PARM4) : 1;
+	while(i < stringbuffer->num_strings)
+	{
+		if (stringbuffer->strings[i] && match_rule(stringbuffer->strings[i], VM_STRINGTEMP_LENGTH, match, matchlen, matchrule))
+		{
+			PRVM_G_FLOAT(OFS_RETURN) = i;
+			break;
+		}
+		i += step;
+	}
+}
+
+/*
+========================
+VM_matchpattern
+float matchpattern(string s, string pattern, float matchrule, float startpos) = #468;
+========================
+*/
+void VM_matchpattern(prvm_prog_t *prog)
+{
+	const char *s, *match;
+	char string[VM_STRINGTEMP_LENGTH];
+	int matchrule, l;
+
+	VM_SAFEPARMCOUNTRANGE(2, 4, VM_matchpattern);
+
+	s = PRVM_G_STRING(OFS_PARM0);
+
+	// get pattern/rule
+	matchrule = (int)PRVM_G_FLOAT(OFS_PARM2);
+	if (matchrule < 0 && matchrule > 5)
+	{
+		VM_Warning(prog, "VM_bufstr_find: invalid match rule %i in %s\n", matchrule, prog->name);
+		return;
+	}
+	if (matchrule)
+		match = PRVM_G_STRING(OFS_PARM1);
+	else
+	{
+		strlcpy(string, PRVM_G_STRING(OFS_PARM1), sizeof(string));
+		match = detect_match_rule(string, &matchrule);
+	}
+
+	// offset
+	l = strlen(match);
+	if (prog->argc > 3)
+		s += max(0, min((unsigned int)PRVM_G_FLOAT(OFS_PARM3), strlen(s)-1));
+
+	// match
+	PRVM_G_FLOAT(OFS_RETURN) = match_rule(s, VM_STRINGTEMP_LENGTH, match, l, matchrule);
+}
+
+/*
+========================
+VM_buf_cvarlist
+========================
+*/
 
 void VM_buf_cvarlist(prvm_prog_t *prog)
 {
diff --git a/prvm_cmds.h b/prvm_cmds.h
index 76793367..39ec4961 100644
--- a/prvm_cmds.h
+++ b/prvm_cmds.h
@@ -410,6 +410,11 @@ void VM_bufstr_set (prvm_prog_t *prog);
 void VM_bufstr_add (prvm_prog_t *prog);
 void VM_bufstr_free (prvm_prog_t *prog);
 
+void VM_buf_loadfile(prvm_prog_t *prog);
+void VM_buf_writefile(prvm_prog_t *prog);
+void VM_bufstr_find(prvm_prog_t *prog);
+void VM_matchpattern(prvm_prog_t *prog);
+
 void VM_changeyaw (prvm_prog_t *prog);
 void VM_changepitch (prvm_prog_t *prog);
 
diff --git a/svvm_cmds.c b/svvm_cmds.c
index 6612f27c..2fd3464c 100644
--- a/svvm_cmds.c
+++ b/svvm_cmds.c
@@ -125,6 +125,7 @@ const char *vm_sv_extensions =
 "DP_QC_STRFTIME "
 "DP_QC_STRINGBUFFERS "
 "DP_QC_STRINGBUFFERS_CVARLIST "
+"DP_QC_STRINGBUFFERS_EXT_WIP "
 "DP_QC_STRINGCOLORFUNCTIONS "
 "DP_QC_STRING_CASE_FUNCTIONS "
 "DP_QC_STRREPLACE "
@@ -3701,10 +3702,10 @@ VM_SV_setpause,					// #531 void(float pause) setpause = #531;
 VM_log,							// #532
 VM_getsoundtime,				// #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME)
 VM_soundlength,					// #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME)
-NULL,							// #535
-NULL,							// #536
-NULL,							// #537
-NULL,							// #538
+VM_buf_loadfile,                // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_buf_writefile,               // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_bufstr_find,                 // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP)
+VM_matchpattern,                // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP)
 NULL,							// #539
 VM_physics_enable,				// #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE)
 VM_physics_addforce,			// #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE)
-- 
2.39.5