ddnet/src/engine/e_console.c

496 lines
9.4 KiB
C

#include "e_system.h"
#include "e_console.h"
#include "e_config.h"
#include "e_linereader.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define CONSOLE_MAX_STR_LENGTH 255
/* 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_TOKENS (CONSOLE_MAX_STR_LENGTH+1)/2
enum
{
TOKEN_INT,
TOKEN_FLOAT,
TOKEN_STRING
};
typedef struct
{
int type;
const char *stored_string;
} TOKEN;
typedef struct
{
char string_storage[CONSOLE_MAX_STR_LENGTH+1];
char *next_string;
TOKEN tokens[MAX_TOKENS];
unsigned int num_tokens;
} LEXER_RESULT;
enum
{
STATE_START,
STATE_INT,
STATE_FLOAT,
STATE_POT_FLOAT,
STATE_POT_NEGATIVE,
STATE_STRING,
STATE_QUOTED,
STATE_ESCAPE
};
static const char *store_string(LEXER_RESULT *res, const char *str, int len)
{
const char *ptr = res->next_string;
int escaped = 0;
while (len)
{
if (!escaped && *str == '\\')
{
escaped = 1;
}
else
{
escaped = 0;
*res->next_string++ = *str;
}
str++;
len--;
}
*res->next_string++ = 0;
/*
memcpy(res->next_string, str, len);
res->next_string[len] = 0;
res->next_string += len+1;
*/
return ptr;
}
static void save_token(LEXER_RESULT *res, int *index, const char **start, const char *end, int *state, int type)
{
/* printf("Saving token with length %d\n", end - *start); */
TOKEN *tok = &res->tokens[*index];
tok->stored_string = store_string(res, *start, end - *start);
tok->type = type;
++res->num_tokens;
*start = end + 1;
*state = STATE_START;
++*index;
}
static int digit(char c)
{
return '0' <= c && c <= '9';
}
static int lex(const char *line, LEXER_RESULT *res)
{
int state = STATE_START, i = 0;
int length_left = CONSOLE_MAX_STR_LENGTH;
const char *start, *c;
res->num_tokens = 0;
mem_zero(res, sizeof(*res));
res->next_string = res->string_storage;
for (c = start = line; *c != '\0' && res->num_tokens < MAX_TOKENS && length_left; ++c, --length_left)
{
/* printf("State: %d.. c: %c\n", state, *c); */
switch (state)
{
case STATE_START:
if (*c == ' ')
start = c + 1;
else if (digit(*c))
state = STATE_INT;
else if (*c == '-')
state = STATE_POT_NEGATIVE;
else if (*c == '.')
state = STATE_POT_FLOAT;
else if (*c == '"')
state = STATE_QUOTED;
else
state = STATE_STRING;
break;
case STATE_POT_NEGATIVE:
if (digit(*c))
state = STATE_INT;
else if (*c == '.')
state = STATE_POT_FLOAT;
else if (*c == ' ')
save_token(res, &i, &start, c, &state, TOKEN_STRING);
else
state = STATE_STRING;
break;
case STATE_INT:
if (digit(*c))
;
else if (*c == '.')
state = STATE_FLOAT;
else if (*c == ' ')
save_token(res, &i, &start, c, &state, TOKEN_INT);
else
state = STATE_STRING;
break;
case STATE_FLOAT:
if (digit(*c))
;
else if (*c == ' ')
save_token(res, &i, &start, c, &state, TOKEN_FLOAT);
else
state = STATE_STRING;
break;
case STATE_POT_FLOAT:
if (digit(*c))
state = STATE_FLOAT;
else if (*c == ' ')
save_token(res, &i, &start, c, &state, TOKEN_STRING);
else
state = STATE_STRING;
break;
case STATE_STRING:
if (*c == ' ')
save_token(res, &i, &start, c, &state, TOKEN_STRING);
break;
case STATE_QUOTED:
if (*c == '"')
{
++start;
save_token(res, &i, &start, c, &state, TOKEN_STRING);
}
else if (*c == '\\')
state = STATE_ESCAPE;
break;
case STATE_ESCAPE:
if (*c != ' ')
state = STATE_QUOTED;
break;
}
}
switch (state)
{
case STATE_INT:
save_token(res, &i, &start, c, &state, TOKEN_INT);
break;
case STATE_FLOAT:
save_token(res, &i, &start, c, &state, TOKEN_FLOAT);
break;
case STATE_STRING:
case STATE_QUOTED:
case STATE_POT_FLOAT:
case STATE_POT_NEGATIVE:
save_token(res, &i, &start, c, &state, TOKEN_STRING);
break;
case STATE_ESCAPE:
dbg_msg("console/lexer", "Misplaced escape character");
break;
default:
break;
}
return 0;
}
int console_result_string(void *res, int index, const char **str)
{
LEXER_RESULT *result = (LEXER_RESULT *)res;
if (index < 0 || index >= result->num_tokens)
return -1;
else
{
TOKEN *t = &result->tokens[index];
*str = t->stored_string;
return 0;
}
}
int console_result_int(void *res, int index, int *i)
{
LEXER_RESULT *result = (LEXER_RESULT *)res;
if (index < 0 || index >= result->num_tokens)
return -1;
else
{
TOKEN *t = &result->tokens[index];
const char *str;
if (t->type != TOKEN_INT)
return -2;
console_result_string(result, index, &str);
*i = atoi(str);
return 0;
}
}
int console_result_float(void *res, int index, float *f)
{
LEXER_RESULT *result = (LEXER_RESULT *)res;
if (index < 0 || index >= result->num_tokens)
return -1;
else
{
TOKEN *t = &result->tokens[index];
const char *str;
if (t->type != TOKEN_INT && t->type != TOKEN_FLOAT)
return -2;
console_result_string(result, index, &str);
*f = atof(str);
return 0;
}
}
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 int console_validate(COMMAND *command, LEXER_RESULT *result)
{
const char *c = command->params;
int i = 1;
const char *dummy_s;
int dummy_i;
float dummy_f;
while (*c && *c != '?')
{
switch (*c)
{
case 's':
if (console_result_string(result, i, &dummy_s))
return -1;
break;
case 'i':
if (console_result_int(result, i, &dummy_i))
return -1;
break;
case 'f':
if (console_result_float(result, i, &dummy_f))
return -1;
break;
default:
/* unknown char, so just continue... */
c++;
continue;
}
i++;
c++;
}
return 0;
}
static void (*print_callback)(const char *) = 0x0;
void console_register_print_callback(void (*callback)(const char *))
{
print_callback = callback;
}
void console_print(const char *str)
{
if (print_callback)
print_callback(str);
}
void console_execute_line_stroked(int stroke, const char *str)
{
LEXER_RESULT result;
int error;
char strokestr[8] = {'0', 0};
if(stroke)
strokestr[0] = '1';
if ((error = lex(str, &result)))
printf("ERROR: %d\n", error);
else if (result.num_tokens > 0)
{
const char *name;
COMMAND *command;
console_result_string(&result, 0, &name);
command = console_find_command(name);
if(command)
{
int is_stroke_command = 0;
if(name[0] == '+')
{
/* insert the stroke direction token */
int i;
for(i = result.num_tokens-2; i > 1; i--)
{
result.tokens[i+1] = result.tokens[i];
}
result.tokens[1].type = TOKEN_INT;
result.tokens[1].stored_string = strokestr;
result.num_tokens++;
is_stroke_command = 1;
}
if(stroke || is_stroke_command)
{
if (console_validate(command, &result))
{
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.", name);
console_print(buf);
}
}
}
void console_execute_line(const char *str)
{
console_execute_line_stroked(1, str);
}
void console_execute_file(const char *filename)
{
IOHANDLE file;
file = io_open(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);
}
static void echo_command(void *result, void *user_data)
{
const char *str;
console_result_string(result, 1, &str);
console_print(str);
}
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;
int new_val;
if (console_result_int(result, 1, &new_val))
{
char buf[256];
str_format(buf, sizeof(buf), "Value: %d", data->getter(&config));
console_print(buf);
}
else
{
data->setter(&config, new_val);
}
}
static void str_variable_command(void *result, void *user_data)
{
STR_VARIABLE_DATA *data = (STR_VARIABLE_DATA *)user_data;
const char *new_val;
if (console_result_string(result, 1, &new_val))
{
char buf[256];
str_format(buf, sizeof(buf), "Value: %s", data->getter(&config));
console_print(buf);
}
else
{
data->setter(&config, new_val);
}
}
void console_init()
{
MACRO_REGISTER_COMMAND("echo", "s", echo_command, 0x0);
#define MACRO_CONFIG_INT(name,def,min,max) { static INT_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?i", int_variable_command, &data) }
#define MACRO_CONFIG_STR(name,len,def) { static STR_VARIABLE_DATA data = { &config_get_ ## name, &config_set_ ## name }; MACRO_REGISTER_COMMAND(#name, "?s", str_variable_command, &data) }
#include "e_config_variables.h"
#undef MACRO_CONFIG_INT
#undef MACRO_CONFIG_STR
}