Files
fs2open.github.com/code/debugconsole/consoleparse.cpp
T

1457 lines
32 KiB
C++

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Command-line parsing functions for z64555's debug console, created for the FreeSpace Source Code project
//
// Portions of this source code are based on works by Volition, Inc. circa 1999. You may not sell or otherwise
// commercially exploit the source or things you created based on the source.
//
// This file contains documentation on locally referenced functions. For documentation on globally referenced
// functions, see consoleparse.h
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////
// @todo Make a fast version of the parse_long, parse_ulong, etc. That just checks the first 1-3 characters. The fast version will be used in retail builds while the slow/safe version will be in debug's
// @todo Maybe make parser functions case-insensitive
////////////////
#include "debugconsole/consoleparse.h"
#include "parse/parselo.h"
#include <algorithm>
#include <cmath>
#include <cstdarg>
#include <cstring>
#include <climits>
// ========================= LOCALS =========================
char Command_string[MAX_CLI_LEN]; //!< Command string buffer.
char *Cp = NULL; //!< Pointer to the commant string
enum state_int {
si_start = 0,
si_end = 1,
si_invalid,
si_sign, //!< Sign character, '-' '+'
si_prefix, //!< prefix character sequence, 0b, 0o, or 0x
si_numeral, //!< Numeral state, 0 - 9
si_numeral_bin, //!< Numeral altstate, 0, 1
si_numeral_octal, //!< Numeral altstate, 0 - 7
si_numeral_hex //!< Numeral altstate, 0 - 9 and 'a' - 'f'
};
enum state_float {
sf_start = 0,
sf_end = 1,
sf_invalid,
sf_sign, //!< Sign character for mantessa
sf_whole, //!< Whole value numeral
sf_decimal, //!< Decimal character
sf_fraction, //!< Fractional value numeral
sf_expprefix, //!< Exponent prefix, 'e', 'E'
sf_expsign, //!< Exponent sign
sf_exponent //!< Exponent value numeral
};
bool ch_is_sign(char ch);
bool ch_is_numeral(char ch);
bool ch_is_decimal(char ch);
bool ch_is_binary(char ch);
bool ch_is_octal(char ch);
bool ch_is_hex(char ch);
bool ch_is_prefix(char ch);
bool ch_is_binary_prefix(char ch);
bool ch_is_octal_prefix(char ch);
bool ch_is_hex_prefix(char ch);
bool ch_is_exp_prefix(char ch);
/**
* @brief Returns/Advances past a single token
*
* @details Similar in operation to dc_stuff_string_white, but won't throw any error messages and will only grab the
* first word. (dc_stuff_string_white may grab quoted strings)
*/
void dc_get_token(SCP_string &out_str);
/**
* @brief Returns a single token, but does not advances Cp
*
* @details Similar in operation to dc_stuff_string_white, but won't throw any error messages and will only grab the
* first word. (dc_stuff_string_white may grab quoted strings).
*/
void dc_get_token_no_advance(SCP_string &out_str);
/**
* @brief Parses a double-precision floating point type. Supports, Whole, Fractional, and Mixed numbers, and supports
* scientific notation (exponent prefixed by 'e' or 'E').
*
* @param[in] ch Points to the start of the string to parse
* @param[in] type The expected type. is thrown along with ch when an unexpected/malformed float is found
*
* @return The value of the parsed token
* @details
* The returned double may be cast to a single-precision float, but be sure to check it before doing so!
*/
double dc_parse_double(const char *ch, dc_token type);
/**
* @brief Parses a long integral type. Supports decimal, binary, octal, and hexidecimal strings.
*
* @param[in] ch Points to the start of the string to parse.
* @param[in] type The expected type. Is thrown along with ch when an unexpected/malformed integral is found
*
* @details
* ! Non-decimal values must be prefixed by their corresponding sequence. Binary: "0b", Octal: "0o", Hex: "0x"
*
* The returned long may be cast to a smaller integral, but be sure to check it before doing so!
*
* The only thing left making this function specific to the DC is the expected type. So, if you want to use this
* for parsing something other than the debug CL, you'll have to make a set of errParse classes that take a
* different expected type.
*/
long dc_parse_long(const char *ch, dc_token type);
/**
* @brief Parses an unsigned long integral type. Supports decimal, binary, octal, and hexidecimal strings.
*
* @param[in] ch Points to the start of the string to parse.
* @param[in] type The expected type. Is thrown along with ch when an unexpected/malformed integral is found
*
* @details
* ! Non-decimal values must be delimited by their corresponding sequence. Binary: "0b", Octal: "0o", Hex: "0x"
* The returned long may be cast to a smaller integral, but be sure to check it before doing so!
* The only thing left making this function specific to the DC is the expected type. So, if you want to use this
* for parsing something other than the debug CL, you'll have to make a set of errParse classes that take a
* different expected type.
*/
ulong dc_parse_ulong(const char *ch, dc_token type);
// State processes for dc_parse_double
state_float dc_parse_double_sign(const char* &ch_ptr, SCP_string &buffer_str);
state_float dc_parse_double_whole(const char* &ch_ptr, SCP_string &buffer_str);
state_float dc_parse_double_decimal(const char* &ch_ptr, SCP_string &buffer_str);
state_float dc_parse_double_fraction(const char* &ch_ptr, SCP_string &buffer_str);
state_float dc_parse_double_expprefix(const char* &ch_ptr, SCP_string &buffer_str);
state_float dc_parse_double_expsign(const char* &ch_ptr, SCP_string &buffer_str);
state_float dc_parse_double_exponent(const char* &ch_ptr, SCP_string &buffer_str);
// State processes for dc_parse_long and dc_parse_ulong.
state_int dc_parse_long_prefix(const char* &ch_ptr, SCP_string &buffer_str, int &base);
state_int dc_parse_long_sign(const char* &ch_ptr, SCP_string &buffer_str);
state_int dc_parse_long_numeral(const char* &ch_ptr, SCP_string &buffer_str);
state_int dc_parse_long_numeral_bin(const char* &ch_ptr, SCP_string &buffer_str);
state_int dc_parse_long_numeral_hex(const char* &ch_ptr, SCP_string &buffer_str);
state_int dc_parse_long_numeral_octal(const char* &ch_ptr, SCP_string &buffer_str);
// ============================== IMPLEMENTATIONS =============================
inline
bool ch_is_sign(char ch)
{
return ((ch == '-') || (ch == '+'));
}
inline
bool ch_is_numeral(char ch)
{
return ((ch >= '0') && (ch <= '9'));
}
inline
bool ch_is_decimal(char ch)
{
return (ch == '.');
}
inline
bool ch_is_binary(char ch)
{
return ((ch == '0') || (ch == '1'));
}
inline
bool ch_is_octal(char ch)
{
return ((ch >= '0') && (ch <= '7'));
}
inline
bool ch_is_hex(char ch)
{
return (((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'f')) || ((ch >= 'A') && (ch <= 'F')));
};
inline
bool ch_is_prefix(char ch)
{
return (ch == '0');
}
inline
bool ch_is_binary_prefix(char ch)
{
return ((ch == 'b') || (ch == 'B'));
}
inline
bool ch_is_octal_prefix(char ch)
{
return ((ch == 'o') || (ch == 'O'));
}
inline
bool ch_is_hex_prefix(char ch)
{
return ((ch == 'x') || (ch == 'X'));
}
inline
bool ch_is_exp_prefix(char ch)
{
return ((ch == 'e') || (ch == 'E'));
}
void dc_get_token(SCP_string &out_str) {
size_t count = 0;
char *c_ptr;
Assert(Cp);
dc_ignore_gray_space();
out_str = "";
// Bail if we're at the terminator
if (*Cp == '\0') {
return;
}
// Scan the string, stopping at grayspace, null terminator, or before we go over MAX_TOKEN_LENGTH
c_ptr = Cp;
while (!is_gray_space(*c_ptr) && (*c_ptr != '\0') && (count < MAX_TOKEN_LENGTH)) {
count++;
c_ptr++;
}
// Copy string into out_str
out_str.assign(Cp, count);
// Advance the parser pointer past what we copied
Cp = c_ptr;
}
void dc_get_token_no_advance(SCP_string &out_str) {
size_t count = 0;
char *c_ptr;
Assert(Cp);
dc_ignore_gray_space();
out_str = "";
// Bail if we're at the terminator
if (*Cp == '\0') {
return;
}
// Scan the string, stopping at grayspace, null terminator, or before we go over MAX_TOKEN_LENGTH
c_ptr = Cp;
while (!is_gray_space(*c_ptr) && (*c_ptr != '\0') && (count < MAX_TOKEN_LENGTH)) {
count++;
c_ptr++;
}
// Copy string into out_str
out_str.assign(Cp, count);
}
void dc_ignore_white_space(void) {
while (is_white_space(*Cp) && (*Cp != '\0')) {
Cp++;
}
}
void dc_ignore_gray_space(void) {
while (is_gray_space(*Cp) && (*Cp != '\0')) {
Cp++;
}
}
bool dc_maybe_stuff_float(float *f)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_float(f);
return true;
} else {
*f = 0;
return false;
}
}
bool dc_maybe_stuff_int(int *i)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_int(i);
return true;
} else {
*i = 0;
return false;
}
}
bool dc_maybe_stuff_uint(uint *i)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_uint(i);
return true;
} else {
*i = 0;
return false;
}
}
bool dc_maybe_stuff_ubyte(ubyte *i)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_ubyte(i);
return true;
} else {
*i = 0;
return false;
}
}
bool dc_maybe_stuff_boolean(bool *b)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_boolean(b);
return true;
} else {
*b = false;
return false;
}
}
bool dc_maybe_stuff_boolean(int *i)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_boolean(i);
return true;
} else {
*i = 0;
return false;
}
}
bool dc_maybe_stuff_string(char *out_str, size_t maxlen)
{
size_t count = 0;
char *c_ptr = Cp;
Assert(Cp);
Assert(out_str);
// Advance past grayspace, stopping at null terminator
while (is_gray_space(*c_ptr) && (*c_ptr != '\0')) {
c_ptr++;
}
// Bail if we're at the terminator
if (*c_ptr == '\0') {
return false;
}
// Scan the string, stopping at null terminator, or before we overflow
while ((*c_ptr != '\0') && (count < maxlen)) {
count++;
c_ptr++;
}
// Bail if overflow
if (count == maxlen) {
throw errParse("", DCT_STRING);
}
// Copy string into out_str
strncpy(out_str, Cp, count);
// Advance the parser pointer past what we copied
Cp = c_ptr;
return true;
}
bool dc_maybe_stuff_string(SCP_string &out_str)
{
size_t count = 0;
char *c_ptr = Cp;
Assert(Cp);
// Advance past grayspace, stopping at null terminator
while (is_gray_space(*c_ptr) && (*c_ptr != '\0')) {
c_ptr++;
}
// Bail if we're at the terminator
if (*c_ptr == '\0') {
return false;
}
// Scan the string, stopping at null terminator, or before we overflow
while ((*c_ptr != '\0') && (count < out_str.max_size())) {
count++;
c_ptr++;
}
// Bail if overflow
if (count == out_str.max_size()) {
return false;
}
// Copy string into out_str
out_str.assign(Cp, count);
// Advance the parser pointer past what we copied
Cp = c_ptr;
return true;
}
bool dc_maybe_stuff_string_white(char *str, size_t len)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_string_white(str, len);
return true;
} else {
*str = '\0';
return false;
}
}
bool dc_maybe_stuff_string_white(SCP_string &str)
{
dc_ignore_gray_space();
if (*Cp != '\0') {
dc_stuff_string_white(str);
return true;
} else {
str = "";
return false;
}
}
void dc_required_string(char *pstr)
{
char *str_found = NULL;
dc_ignore_gray_space();
if (strncmp(pstr, Cp, strlen(pstr)) == 0) {
str_found = pstr;
}
if (str_found != NULL) {
// Found a required string
if (Dc_debug_on) {
dc_printf("<debug> Found required string [%s]\n", str_found);
}
Cp += strlen(str_found);
} else {
// Didn't find a required string.
SCP_string token;
dc_get_token_no_advance(token);
throw errParseString(token.c_str(), pstr);
}
}
int dc_required_string_either(char *str1, char *str2)
{
char *str_found = NULL;
int i = -1;
dc_ignore_gray_space();
if (strncmp(str1, Cp, strlen(str1)) == 0) {
str_found = str1;
i = 0;
} else if (strncmp(str2, Cp, strlen(str2)) == 0) {
str_found = str2;
i = 1;
}
if (i > -1) {
// Found a required string
if (Dc_debug_on) {
dc_printf("<debug> Found required string [%s]\n", str_found);
}
Cp += strlen(str_found);
} else {
// Didn't find a required string.
SCP_string token;
dc_get_token_no_advance(token);
throw errParseString(token.c_str(), str1, str2);
}
return i;
}
uint dc_required_string_any(const uint n, ...)
{
va_list vl;
SCP_vector<SCP_string> strings;
const char *str_found = NULL;
uint i;
dc_ignore_gray_space();
// Populate the vector.
va_start(vl, n);
for (i = 0; i < n; ++i) {
strings.push_back(va_arg(vl, char *));
}
va_end(vl);
// Search for the required string. If found, i = index of the string passed
for (i = 0; i < n; ++i) {
if (strcmp(Cp, strings[i].c_str()) == 0)
{
str_found = strings[i].c_str();
break;
}
}
if (str_found != NULL) {
// Found a required string
if (Dc_debug_on) {
dc_printf("<debug> Found required string [%s}\n", str_found);
}
Cp += strlen(str_found);
} else {
// Didn't find a required string.
SCP_string token;
dc_get_token_no_advance(token);
throw errParseString(token.c_str(), strings);
}
return i;
}
bool dc_optional_string(const char *pstr)
{
dc_ignore_gray_space();
if (strncmp(pstr, Cp, strlen(pstr)) != 0) {
return false;
} // Else, optional string was found
if (Dc_debug_on) {
dc_printf("<debug> Found optional string [%s]\n", pstr);
}
Cp += strlen(pstr);
return true;
}
bool dc_optional_string_either(const char *str1, const char *str2)
{
const char *str_found = NULL;
dc_ignore_gray_space();
if (strncmp(str1, Cp, strlen(str1)) == 0) {
str_found = str1;
} else if (strncmp(str2, Cp, strlen(str2)) == 0) {
str_found = str2;
} else {
return false;
}
if (Dc_debug_on) {
dc_printf("<debug> Found optional string [%s]\n",str_found);
}
Cp += strlen(str_found);
return true;;
}
void dc_parse_init(SCP_string &str)
{
strcpy_s(Command_string, str.c_str());
Cp = Command_string;
}
double dc_parse_double(const char *ch, dc_token type) {
const char *ch_ptr = ch;
char *end_ptr;
double ret;
state_float state = sf_start;
SCP_string buffer_str;
while ((*ch_ptr != '\0') && is_white_space(*ch_ptr)) {
ch_ptr++;
}
if (*ch_ptr == '\0') {
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] no argument found\n");
}
throw errParse("", type);
}
do {
switch (state) {
case sf_start:
if (ch_is_sign(*ch_ptr)) {
state = sf_sign;
if (*ch_ptr == '-') {
buffer_str.push_back(*ch_ptr);
} // Else, sign is positive, and isn't needed on the buffer
ch_ptr++;
} else if (ch_is_numeral(*ch_ptr)) {
state = sf_whole;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_decimal(*ch_ptr)) {
state = sf_decimal;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else {
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_start\n", *ch_ptr);
}
state = sf_invalid;
}
break;
case sf_end:
if (buffer_str == "") {
state = sf_invalid;
} // Else, we can convert the token.
// Do nothing, and allow the while loop exit condition to trigger
break;
case sf_sign:
state = dc_parse_double_sign(ch_ptr, buffer_str);
break;
case sf_whole:
state = dc_parse_double_whole(ch_ptr, buffer_str);
break;
case sf_decimal:
state = dc_parse_double_decimal(ch_ptr, buffer_str);
break;
case sf_fraction:
state = dc_parse_double_fraction(ch_ptr, buffer_str);
break;
case sf_expprefix:
state = dc_parse_double_expprefix(ch_ptr, buffer_str);
break;
case sf_expsign:
state = dc_parse_double_expsign(ch_ptr, buffer_str);
break;
case sf_exponent:
state = dc_parse_double_exponent(ch_ptr, buffer_str);
break;
case sf_invalid:
default:
throw errParse(ch, type);
}
} while (state != sf_end);
ret = strtod(buffer_str.c_str(), &end_ptr);
return ret;
}
state_float dc_parse_double_sign(const char* &ch_ptr, SCP_string &buffer_str)
{
state_float state = sf_invalid;
if (ch_is_numeral(*ch_ptr)) {
state = sf_whole;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_decimal(*ch_ptr)) {
state = sf_decimal;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else {
state = sf_invalid;
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_sign\n", *ch_ptr);
}
}
return state;
}
state_float dc_parse_double_whole(const char* &ch_ptr, SCP_string &buffer_str)
{
state_float state = sf_invalid;
if (ch_is_numeral(*ch_ptr)) {
state = sf_whole;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_decimal(*ch_ptr)) {
state = sf_decimal;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_exp_prefix(*ch_ptr)) {
state = sf_expprefix;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = sf_end;
} else {
state = sf_invalid;
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_whole\n", *ch_ptr);
}
}
return state;
}
state_float dc_parse_double_decimal(const char* &ch_ptr, SCP_string &buffer_str)
{
state_float state = sf_invalid;
if (ch_is_numeral(*ch_ptr)) {
state = sf_fraction;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_exp_prefix(*ch_ptr)) {
state = sf_expprefix;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = sf_end;
} else {
state = sf_invalid;
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_decimal\n", *ch_ptr);
}
}
return state;
}
state_float dc_parse_double_fraction(const char* &ch_ptr, SCP_string &buffer_str)
{
state_float state = sf_invalid;
if (ch_is_numeral(*ch_ptr)) {
state = sf_fraction;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_exp_prefix(*ch_ptr)) {
state = sf_expprefix;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = sf_end;
} else {
state = sf_invalid;
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_faction\n", *ch_ptr);
}
}
return state;
}
state_float dc_parse_double_expprefix(const char* &ch_ptr, SCP_string &buffer_str)
{
state_float state = sf_invalid;
if (ch_is_sign(*ch_ptr)) {
state = sf_expsign;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_numeral(*ch_ptr)) {
state = sf_exponent;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else {
state = sf_invalid;
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_expprefix\n", *ch_ptr);
}
}
return state;
}
state_float dc_parse_double_expsign(const char* &ch_ptr, SCP_string &buffer_str)
{
state_float state = sf_invalid;
if (ch_is_numeral(*ch_ptr)) {
state = sf_exponent;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else {
state = sf_invalid;
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_expsign\n", *ch_ptr);
}
}
return state;
}
state_float dc_parse_double_exponent(const char* &ch_ptr, SCP_string &buffer_str)
{
state_float state = sf_invalid;
if (ch_is_numeral(*ch_ptr)) {
state = sf_exponent;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = sf_end;
} else {
state = sf_invalid;
if (Dc_debug_on) {
dc_printf("<debug> [parse_double] Invalid character '%c' found in sf_exponent\n", *ch_ptr);
}
}
return state;
}
long dc_parse_long(const char *ch, dc_token type) {
const char *ch_ptr = ch;
char *end_ptr = NULL;
int base = 10;
long ret;
state_int state = si_start;
SCP_string buffer_str;
while ((*ch_ptr != '\0') && is_white_space(*ch_ptr)) {
ch_ptr++;
}
if (*ch_ptr == '\0') {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] no argument found\n");
}
throw errParse("", type);
}
do {
switch (state) {
case si_start:
if (ch_is_sign(*ch_ptr)) {
state = si_sign;
if (*ch_ptr == '-') {
buffer_str.push_back(*ch_ptr);
} // Else, it's positive. Positive sign isn't needed for conversion
ch_ptr++;
} else if (ch_is_prefix(*ch_ptr)) {
// prefixes must be checked before numeral, because they all start with a numeral zero
state = si_prefix;
ch_ptr++;
} else if (ch_is_numeral(*ch_ptr)) {
state = si_numeral;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_start\n", *ch_ptr);
}
state = si_invalid;
}
break;
case si_end:
if (buffer_str == "") {
state = si_invalid;
} // Else, we can convert the token.
// Do nothing, and allow the while loop exit condition to trigger
break;
case si_sign:
state = dc_parse_long_sign(ch_ptr, buffer_str);
break;
case si_prefix:
state = dc_parse_long_prefix(ch_ptr, buffer_str, base);
break;
case si_numeral_bin:
state = dc_parse_long_numeral_bin(ch_ptr, buffer_str);
break;
case si_numeral_octal:
state = dc_parse_long_numeral_octal(ch_ptr, buffer_str);
break;
case si_numeral_hex:
state = dc_parse_long_numeral_hex(ch_ptr, buffer_str);
break;
case si_numeral:
state = dc_parse_long_numeral(ch_ptr, buffer_str);
break;
case si_invalid:
default:
throw errParse(ch, type);
break;
}
} while (state != si_end);
ret = strtol(buffer_str.c_str(), &end_ptr, base);
// This last check can be omitted once I can verify the operation of strtol in this sense
if (*end_ptr != '\0') {
dc_printf("Error: Could not convert all of the buffer '%s'.\n", buffer_str.c_str());
if (Dc_debug_on) {
dc_printf("<debug> Buffer value: %s\n", buffer_str.c_str());
dc_printf("<debug> Return value: %ld", ret);
}
throw errParse(ch, type);
}
return ret;
}
ulong dc_parse_ulong(const char *ch, dc_token type) {
const char *ch_ptr = ch;
char *end_ptr;
int base = 10;
ulong ret;
state_int state = si_start;
SCP_string buffer_str;
while ((*ch_ptr != '\0') && is_white_space(*ch_ptr)) {
ch_ptr++;
}
if (*ch_ptr == '\0') {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] no argument found\n");
}
throw errParse("", type);
}
do {
switch (state) {
case si_start:
if (ch_is_prefix(*ch_ptr)) {
// prefixes must be checked before numeral, because they all start with a numeral zero
state = si_prefix;
ch_ptr++;
} else if (ch_is_numeral(*ch_ptr)) {
state = si_numeral;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_start\n", *ch_ptr);
}
state = si_invalid;
}
break;
case si_end:
if (buffer_str == "")
{
state = si_invalid;
} // Else, we can convert the token.
// Do nothing, and allow the while loop exit condition to trigger
break;
case si_prefix:
state = dc_parse_long_prefix(ch_ptr, buffer_str, base);
break;
case si_numeral_bin:
state = dc_parse_long_numeral_bin(ch_ptr, buffer_str);
break;
case si_numeral_octal:
state = dc_parse_long_numeral_octal(ch_ptr, buffer_str);
break;
case si_numeral_hex:
state = dc_parse_long_numeral_hex(ch_ptr, buffer_str);
break;
case si_numeral:
state = dc_parse_long_numeral(ch_ptr, buffer_str);
break;
case si_invalid:
default:
throw errParse(ch, type);
}
} while (state != si_end);
ret = strtoul(buffer_str.c_str(), &end_ptr, base);
// This last check can be omitted once I can verify the operation of strtol in this sense
if (end_ptr != ch_ptr) {
dc_printf("Error: Could not convert all of the buffer '%s'.\n", buffer_str.c_str());
if (Dc_debug_on) {
dc_printf("<debug> Buffer value: %s\n", buffer_str.c_str());
dc_printf("<debug> Return value: %ld", ret);
}
throw errParse(ch, type);
}
return ret;
}
void dc_stuff_float(float *f)
{
double value_d;
SCP_string token;
dc_ignore_gray_space();
dc_get_token(token); // Grab the token
value_d = dc_parse_double(token.c_str(), DCT_FLOAT); // Parse and convert
// Stuff value if within bounds
if ((std::abs(value_d) < FLT_MAX) && (std::abs(value_d) > FLT_MIN)) {
*f = static_cast<float>(value_d);
} else {
throw errParse(token.c_str(), DCT_FLOAT);
}
}
void dc_stuff_int(int *i)
{
long value_l;
SCP_string token;
dc_ignore_gray_space();
dc_get_token(token);
value_l = dc_parse_long(token.c_str(), DCT_INT);
if ((value_l < INT_MAX) && (value_l > INT_MIN)) {
*i = value_l;
} else {
throw errParse(token.c_str(), DCT_INT);
}
}
void dc_stuff_uint(uint *i)
{
ulong value_l;
SCP_string token;
dc_ignore_gray_space();
dc_get_token(token);
value_l = dc_parse_long(Cp, DCT_INT);
if (value_l < UINT_MAX) {
*i = value_l;
} else {
throw errParse(token.c_str(), DCT_INT);
}
}
void dc_stuff_ubyte(ubyte *i)
{
ulong value_ul;
SCP_string token;
dc_ignore_gray_space();
dc_get_token(token);
value_ul = dc_parse_ulong(Cp, DCT_UBYTE);
// Since some system's chars may be greater than 1 byte, we can't use UCHAR_MAX for a UBYTE
if (value_ul <= 255) {
*i = static_cast<ubyte>(value_ul);
} else {
throw errParse(token.c_str(), DCT_UBYTE);
}
}
void dc_stuff_boolean(bool *b)
{
SCP_string token;
dc_get_token(token);
if ((token == "yes")
|| (token == "true")
|| (token == "ja") // German
|| (token == "Oui") // French
|| (token == "si") // Spanish
// || (token == "ita vero") // Latin, not supported
|| (token == "HIja") || (token == "HISLaH") // Klingon
|| (token == "1"))
{
*b = true;
} else if ((token == "no")
|| (token == "false")
|| (token == "nein") // German
|| (token == "Non") // French
// || (token == "no") // Spanish, redundant with English "no"
// || (token == "minime") // Latin, not supported
|| (token == "ghobe'") // Klingon
|| (token == "0"))
{
*b = false;
} else {
throw errParse(token.c_str(), DCT_BOOL);
}
}
void dc_stuff_boolean(int *i)
{
bool value_b;
dc_stuff_boolean(&value_b);
value_b ? *i = 1 : *i = 0;
}
void dc_stuff_string(char *out_str, size_t maxlen = MAX_TOKEN_LENGTH)
{
size_t count = 0;
char *c_ptr = Cp;
SCP_string token;
Assert(Cp);
Assert(out_str);
dc_ignore_gray_space();
// Bail if we're at the terminator
if (*Cp == '\0') {
throw errParse("Nothing!", DCT_STRING);
}
// Scan the string, stopping at null terminator, or before we overflow
c_ptr = Cp;
while ((*c_ptr != '\0') && (count < maxlen)) {
count++;
c_ptr++;
}
// Bail if overflow
if (count == maxlen) {
token.assign(Cp, maxlen);
throw errParseOverflow(token.c_str(), maxlen);
}
// Copy string into out_str
strncpy(out_str, Cp, count);
// Advance the parser pointer past what we copied
Cp = c_ptr;
}
void dc_stuff_string(SCP_string &out_str)
{
size_t count = 0;
char *c_ptr = Cp;
Assert(Cp);
dc_ignore_gray_space();
// Bail if we're at the terminator
if (*Cp == '\0') {
throw errParse("Nothing!", DCT_STRING);
}
// Scan the string, stopping at null terminator, or before we overflow
c_ptr = Cp;
while ((*c_ptr != '\0') && (count < out_str.max_size())) {
count++;
c_ptr++;
}
// Bail if overflow
if ((count == out_str.max_size()) && (*c_ptr != '\0')) {
throw errParse("SCP_string overflow!", DCT_STRING);
}
// Copy string into out_str
out_str.assign(Cp, count);
// Advance the parser pointer past what we copied
Cp = c_ptr;
}
void dc_stuff_string_white(char *out_str, size_t maxlen)
{
size_t count = 0;
char *c_ptr = Cp;
Assert(Cp);
Assert(out_str);
dc_ignore_gray_space();
// Bail if we're at the terminator
if (*Cp == '\0') {
throw errParse("Nothing!", DCT_STRING);
}
// Scan the string, stopping at grayspace, null terminator, or before we overflow
c_ptr = Cp;
while (!is_gray_space(*c_ptr) && (*c_ptr != '\0') && (count < maxlen)) {
count++;
c_ptr++;
}
// Bail if overflow
if (count == maxlen) {
throw errParse("", DCT_STRING);
}
// Copy string into out_str
strncpy(out_str, Cp, count);
// Advance the parser pointer past what we copied
Cp = c_ptr;
}
void dc_stuff_string_white(SCP_string &out_str)
{
size_t count = 0;
char *c_ptr;
Assert(Cp);
dc_ignore_gray_space();
// Bail if we're at the terminator
if (*Cp == '\0') {
throw errParse("Nothing!", DCT_STRING);
}
// Scan the string, stopping at grayspace, null terminator, or before we overflow
c_ptr = Cp;
while (!is_gray_space(*c_ptr) && (*c_ptr != '\0') && (count < out_str.max_size())) {
count++;
c_ptr++;
}
// Bail if overflow
if (count == out_str.max_size()) {
throw errParse("", DCT_STRING);
}
// Copy string into out_str
out_str.assign(Cp, count);
// Advance the parser pointer past what we copied
Cp = c_ptr;
}
inline
state_int dc_parse_long_prefix(const char* &ch_ptr, SCP_string &buffer_str, int &base) {
state_int state = si_invalid;
if (ch_is_binary_prefix(*ch_ptr)) {
state = si_numeral_bin;
base = 2;
ch_ptr++;
} else if (ch_is_octal_prefix(*ch_ptr)) {
state = si_numeral_octal;
base = 8;
ch_ptr++;
} else if (ch_is_hex_prefix(*ch_ptr)) {
state = si_numeral_hex;
base = 16;
ch_ptr++;
} else if (ch_is_numeral(*ch_ptr)) {
// User passed something like 0123
// Just ignore the first 0, if the user passed something like 00001 then state si_numeral can handle it
state = si_numeral;
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
// Just a 0 was passed.
buffer_str.push_back('0');
state = si_end;
} else {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_prefix\n", *ch_ptr);
}
state = si_invalid;
}
return state;
}
inline
state_int dc_parse_long_sign(const char* &ch_ptr, SCP_string &buffer_str) {
state_int state;
if (ch_is_numeral(*ch_ptr)) {
state = si_numeral;
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if (ch_is_prefix(*ch_ptr)) {
state = si_prefix;
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
// Error, no value found
state = si_invalid;
} else {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_sign\n", *ch_ptr);
}
state = si_invalid;
}
return state;
}
inline
state_int dc_parse_long_numeral(const char* &ch_ptr, SCP_string &buffer_str) {
state_int state = si_numeral;
if (ch_is_numeral(*ch_ptr)) {
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = si_end;
} else {
// Invalid character, throw and bail
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_numeral\n", *ch_ptr);
}
state = si_invalid;
}
return state;
}
inline
state_int dc_parse_long_numeral_bin(const char* &ch_ptr, SCP_string &buffer_str) {
state_int state = si_numeral_bin;
if (ch_is_binary(*ch_ptr)) {
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = si_end;
} else {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_numeral_bin\n", *ch_ptr);
}
state = si_invalid;
}
return state;
}
inline
state_int dc_parse_long_numeral_hex(const char* &ch_ptr, SCP_string &buffer_str) {
state_int state = si_numeral_hex;
if (ch_is_hex(*ch_ptr)) {
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = si_end;
} else {
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_numeral_hex\n", *ch_ptr);
}
state = si_invalid;
}
return state;
}
inline
state_int dc_parse_long_numeral_octal(const char* &ch_ptr, SCP_string &buffer_str) {
state_int state = si_numeral_octal;
if (ch_is_octal(*ch_ptr)) {
buffer_str.push_back(*ch_ptr);
ch_ptr++;
} else if ((*ch_ptr == '\0') || is_white_space(*ch_ptr)) {
state = si_end;
} else {
// Invalid character, throw and bail
if (Dc_debug_on) {
dc_printf("<debug> [parse_long] Invalid character '%c' found in si_numeral_octal\n", *ch_ptr);
}
state = si_invalid;
}
return state;
}