about summary refs log tree commit diff
path: root/src/engine/e_console.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/e_console.cpp')
-rw-r--r--src/engine/e_console.cpp464
1 files changed, 464 insertions, 0 deletions
diff --git a/src/engine/e_console.cpp b/src/engine/e_console.cpp
new file mode 100644
index 00000000..c641289d
--- /dev/null
+++ b/src/engine/e_console.cpp
@@ -0,0 +1,464 @@
+#include <base/system.h>
+#include "e_if_other.h"
+#include "e_console.h"
+#include "e_config.h"
+#include "e_engine.h"
+#include "e_linereader.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+
+#define CONSOLE_MAX_STR_LENGTH 1024
+/* the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces */
+#define MAX_PARTS (CONSOLE_MAX_STR_LENGTH+1)/2
+
+typedef struct
+{ 
+	char string_storage[CONSOLE_MAX_STR_LENGTH+1];
+	char *args_start;
+	
+	const char *command;
+	const char *args[MAX_PARTS]; 
+	unsigned int num_args; 
+} PARSE_RESULT;
+
+static char *str_skipblanks(char *str)
+{
+	while(*str && (*str == ' ' || *str == '\t' || *str == '\n'))
+		str++;
+	return str;
+}
+
+static char *str_skiptoblank(char *str)
+{
+	while(*str && (*str != ' ' && *str != '\t' && *str != '\n'))
+		str++;
+	return str;
+}
+
+/* static int digit(char c) { return '0' <= c && c <= '9'; } */
+
+static int console_parse_start(PARSE_RESULT *result, const char *string, int length)
+{
+	char *str;
+	int len = sizeof(result->string_storage);
+	if(length < len)
+		len = length;
+		
+	str_copy(result->string_storage, string, length);
+	str = result->string_storage;
+	
+	/* get command */
+	str = str_skipblanks(str);
+	result->command = str;
+	str = str_skiptoblank(str);
+	
+	if(*str)
+	{
+		str[0] = 0;
+		str++;
+	}
+	
+	result->args_start = str;
+	result->num_args = 0;
+	return 0;
+}
+
+static int console_parse_args(PARSE_RESULT *result, const char *format)
+{
+	char command;
+	char *str;
+	int optional = 0;
+	int error = 0;
+	
+	str = result->args_start;
+
+	while(1)	
+	{
+		/* fetch command */
+		command = *format;
+		format++;
+		
+		if(!command)
+			break;
+		
+		if(command == '?')
+			optional = 1;
+		else
+		{
+			str = str_skipblanks(str);
+		
+			if(!(*str)) /* error, non optional command needs value */
+			{
+				if(!optional)
+					error = 1;
+				break;
+			}
+			
+			/* add token */
+			if(*str == '"')
+			{
+				char *dst;
+				str++;
+				result->args[result->num_args++] = str;
+				
+				dst = str; /* we might have to process escape data */
+				while(1)
+				{
+					if(str[0] == '"')
+						break;
+					else if(str[0] == '\\')
+					{
+						if(str[1] == '\\')
+							str++; /* skip due to escape */
+						else if(str[1] == '"')
+							str++; /* skip due to escape */
+					}
+					else if(str[0] == 0)
+						return 1; /* return error */
+						
+					*dst = *str;
+					dst++;
+					str++;
+				}
+				
+				/* write null termination */
+				*dst = 0;
+			}
+			else
+			{
+				result->args[result->num_args++] = str;
+				
+				if(command == 'r') /* rest of the string */
+					break;
+				else if(command == 'i') /* validate int */
+					str = str_skiptoblank(str);
+				else if(command == 'f') /* validate float */
+					str = str_skiptoblank(str);
+				else if(command == 's') /* validate string */
+					str = str_skiptoblank(str);
+
+				if(str[0] != 0) /* check for end of string */
+				{
+					str[0] = 0;
+					str++;
+				}
+			}
+		}
+	}
+
+	return error;
+}
+
+const char *console_arg_string(void *res, unsigned index)
+{
+	PARSE_RESULT *result = (PARSE_RESULT *)res;
+	if (index < 0 || index >= result->num_args)
+		return "";
+	return result->args[index];
+}
+
+int console_arg_int(void *res, unsigned index)
+{
+	PARSE_RESULT *result = (PARSE_RESULT *)res;
+	if (index < 0 || index >= result->num_args)
+		return 0;
+	return atoi(result->args[index]);
+}
+
+float console_arg_float(void *res, unsigned index)
+{
+	PARSE_RESULT *result = (PARSE_RESULT *)res;
+	if (index < 0 || index >= result->num_args)
+		return 0.0f;
+	return atof(result->args[index]);
+}
+
+int console_arg_num(void *result)
+{
+	return ((PARSE_RESULT *)result)->num_args;
+}
+
+static COMMAND *first_command = 0x0;
+
+COMMAND *console_find_command(const char *name)
+{
+	COMMAND *cmd;
+	for (cmd = first_command; cmd; cmd = cmd->next)
+	{
+		if (strcmp(cmd->name, name) == 0)
+			return cmd;
+	}
+
+	return 0x0;
+}
+
+void console_register(COMMAND *cmd)
+{
+	cmd->next = first_command;
+	first_command = cmd;
+}
+
+static void (*print_callback)(const char *, void *) = 0x0;
+static void *print_callback_userdata;
+
+void console_register_print_callback(void (*callback)(const char *, void *), void *user_data)
+{
+	print_callback = callback;
+	print_callback_userdata = user_data;
+}
+
+void console_print(const char *str)
+{
+	if (print_callback)
+		print_callback(str, print_callback_userdata);
+}
+
+void console_execute_line_stroked(int stroke, const char *str)
+{
+	PARSE_RESULT result;
+	COMMAND *command;
+	
+	char strokestr[2] = {'0', 0};
+	if(stroke)
+		strokestr[0] = '1';
+
+	while(str)
+	{
+		const char *end = str;
+		const char *next_part = 0;
+		int in_string = 0;
+		
+		while(*end)
+		{
+			if(*end == '"')
+				in_string ^= 1;
+			else if(*end == '\\') /* escape sequences */
+			{
+				if(end[1] == '"')
+					end++;
+			}
+			else if(!in_string)
+			{
+				if(*end == ';')  /* command separator */
+				{
+					next_part = end+1;
+					break;
+				}
+				else if(*end == '#')  /* comment, no need to do anything more */
+					break;
+			}
+			
+			end++;
+		}
+		
+		if(console_parse_start(&result, str, (end-str) + 1) != 0)
+			return;
+
+		command = console_find_command(result.command);
+
+		if(command)
+		{
+			int is_stroke_command = 0;
+			if(result.command[0] == '+')
+			{
+				/* insert the stroke direction token */
+				result.args[result.num_args] = strokestr;
+				result.num_args++;
+				is_stroke_command = 1;
+			}
+			
+			if(stroke || is_stroke_command)
+			{
+				if(console_parse_args(&result, command->params))
+				{
+					char buf[256];
+					str_format(buf, sizeof(buf), "Invalid arguments... Usage: %s %s", command->name, command->params);
+					console_print(buf);
+				}
+				else
+					command->callback(&result, command->user_data);
+			}
+		}
+		else
+		{
+			char buf[256];
+			str_format(buf, sizeof(buf), "No such command: %s.", result.command);
+			console_print(buf);
+		}
+		
+		str = next_part;
+	}
+}
+
+void console_possible_commands(const char *str, int flagmask, void (*callback)(const char *cmd, void *user), void *user)
+{
+	COMMAND *cmd;
+	for (cmd = first_command; cmd; cmd = cmd->next)
+	{
+		if(cmd->flags&flagmask)
+		{
+			if(str_find_nocase(cmd->name, str))
+				callback(cmd->name, user);
+		}
+	}	
+}
+
+
+COMMAND *console_get_command(const char *str)
+{
+	COMMAND *cmd;
+	for (cmd = first_command; cmd; cmd = cmd->next)
+	{
+		if(str_comp_nocase(cmd->name, str) == 0)
+			return cmd;
+	}	
+	
+	return 0x0;
+}
+
+void console_execute_line(const char *str)
+{
+	console_execute_line_stroked(1, str);
+}
+
+static void console_execute_file_real(const char *filename)
+{
+	IOHANDLE file;
+	file = engine_openfile(filename, IOFLAG_READ);
+	
+	if(file)
+	{
+		char *line;
+		LINEREADER lr;
+		
+		dbg_msg("console", "executing '%s'", filename);
+		linereader_init(&lr, file);
+
+		while((line = linereader_get(&lr)))
+			console_execute_line(line);
+
+		io_close(file);
+	}
+	else
+		dbg_msg("console", "failed to open '%s'", filename);
+}
+
+struct EXECFILE
+{
+	const char *filename;
+	struct EXECFILE *next;
+};
+
+void console_execute_file(const char *filename)
+{
+	static struct EXECFILE *first = 0;
+	struct EXECFILE this_file;
+	struct EXECFILE *cur;
+	struct EXECFILE *prev;
+
+	/* make sure that this isn't being executed already */	
+	for(cur = first; cur; cur = cur->next)
+		if(strcmp(filename, cur->filename) == 0)
+			return;
+	
+	/* push this one to the stack */
+	prev = first;
+	this_file.filename = filename;
+	this_file.next = first;
+	first = &this_file;
+	
+	/* execute file */
+	console_execute_file_real(filename);
+	
+	/* pop this one from the stack */
+	first = prev;
+}
+
+static void con_echo(void *result, void *user_data)
+{
+	console_print(console_arg_string(result, 0));
+}
+
+static void con_exec(void *result, void *user_data)
+{
+	console_execute_file(console_arg_string(result, 0));
+
+}
+
+
+typedef struct 
+{
+	CONFIG_INT_GETTER getter;
+	CONFIG_INT_SETTER setter;
+} INT_VARIABLE_DATA;
+
+typedef struct
+{
+	CONFIG_STR_GETTER getter;
+	CONFIG_STR_SETTER setter;
+} STR_VARIABLE_DATA;
+
+static void int_variable_command(void *result, void *user_data)
+{
+	INT_VARIABLE_DATA *data = (INT_VARIABLE_DATA *)user_data;
+
+	if(console_arg_num(result))
+		data->setter(&config, console_arg_int(result, 0));
+	else
+	{
+		char buf[1024];
+		str_format(buf, sizeof(buf), "Value: %d", data->getter(&config));
+		console_print(buf);
+	}
+}
+
+static void str_variable_command(void *result, void *user_data)
+{
+	STR_VARIABLE_DATA *data = (STR_VARIABLE_DATA *)user_data;
+
+	if(console_arg_num(result))
+		data->setter(&config, console_arg_string(result, 0));
+	else
+	{
+		char buf[1024];
+		str_format(buf, sizeof(buf), "Value: %s", data->getter(&config));
+		console_print(buf);
+	}
+}
+
+static void console_chain(void *result, void *user_data)
+{
+	COMMANDCHAIN *info = (COMMANDCHAIN *)user_data;
+	info->chain_callback(result, info->user_data, info->callback, info->callback_user_data);
+}
+
+void console_chain_command(const char *cmd, COMMANDCHAIN *chaininfo, CONSOLE_CHAIN_CALLBACK cb, void *user)
+{
+	COMMAND *command = console_get_command(cmd);
+
+	/* store info */
+	chaininfo->chain_callback = cb;
+	chaininfo->callback = command->callback;
+	chaininfo->callback_user_data = command->user_data;
+	chaininfo->user_data = user;
+	
+	/* chain */
+	command->callback = console_chain;
+	command->user_data = chaininfo;
+}
+
+void console_init()
+{
+	MACRO_REGISTER_COMMAND("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_echo, 0x0, "Echo the text");
+	MACRO_REGISTER_COMMAND("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_exec, 0x0, "Execute the specified file");
+
+	#define MACRO_CONFIG_INT(name,def,min,max,flags,desc) { static INT_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?i", flags, int_variable_command, &data, desc) }
+	#define MACRO_CONFIG_STR(name,len,def,flags,desc) { static STR_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?r", flags, str_variable_command, &data, desc) }
+
+	#include "e_config_variables.h" 
+
+	#undef MACRO_CONFIG_INT 
+	#undef MACRO_CONFIG_STR 
+}