src/z-dice.c
bool dice_parse_string(dice_t *dice, const char *string)
{
	char token[DICE_TOKEN_SIZE + 1] = { '\0' };
	size_t token_end = 0;
	size_t current = 0;
	dice_state_t state = 0;

	/* We need to keep track of the last thing we saw, since the parser isn't complex. */
	enum last_seen_e {
		DICE_SEEN_NONE,
		DICE_SEEN_BASE,
		DICE_SEEN_DICE,
		DICE_SEEN_SIDE,
		DICE_SEEN_BONUS,
	} last_seen = DICE_SEEN_NONE;

	if (dice == NULL || string == NULL)
		return FALSE;

	/* Reset all internal state, since this object might be reused. */
	dice_reset(dice);

	/* Note that we are including the string terminator as part of the parse. */
	for (current = 0; current <= strlen(string); current++) {
		bool flush;
		dice_input_t input_type = DICE_INPUT_MAX;

		/* Skip spaces; this will concatenate digits and variable names. */
		if (isspace(string[current]))
			continue;

		input_type = dice_input_for_char(string[current]);

		/*
		 * Get the next state, based on the type of input char. If it's a
		 * possible number or varible name, we'll store the character in the
		 * token buffer.
		 */
		switch (input_type) {
			case DICE_INPUT_AMP:
			case DICE_INPUT_BASE:
			case DICE_INPUT_DICE:
			case DICE_INPUT_VAR:
			case DICE_INPUT_NULL:
				state = dice_parse_state_transition(state, input_type);
				break;

			case DICE_INPUT_MINUS:
			case DICE_INPUT_DIGIT:
			case DICE_INPUT_UPPER:
				/* Truncate tokens if they are too long to fit. */
				if (token_end < DICE_TOKEN_SIZE) {
					token[token_end] = string[current];
					token_end++;
				}

				state = dice_parse_state_transition(state, input_type);
				break;

			default:
				break;
		}

		/*
		 * Allow 'M' to be used as the bonus marker and to be used in variable
		 * names.
		 * Ideally, 'm' should be the only marker and this could go away by
		 * adding a case to the switch above for DICE_INPUT_BONUS
		 * (underneath DICE_INPUT_NULL).
		 */
		if (string[current] == 'M') {
			if (state == DICE_STATE_VAR || state == DICE_STATE_VAR_CHAR) {
				if (token_end < DICE_TOKEN_SIZE) {
					token[token_end] = string[current];
					token_end++;
				}

				state = dice_parse_state_transition(state, DICE_INPUT_UPPER);
			}
			else
				state = dice_parse_state_transition(state, DICE_INPUT_BONUS);
		}
		else if (string[current] == 'm') {
			state = dice_parse_state_transition(state, DICE_INPUT_BONUS);
		}

		/* Illegal transition. */
		if (state >= DICE_STATE_MAX)
			return FALSE;

		/*
		 * Default flushing to true, since there are more states that don't
		 * need to be flushed. For some states, we need to do a bit of extra
		 * work, since the parser isn't that complex. A more complex parser
		 * would have more explicit states for variable names.
		 */
		flush = TRUE;

		switch (state) {
			case DICE_STATE_FLUSH_BASE:
				last_seen = DICE_SEEN_BASE;
				break;

			case DICE_STATE_FLUSH_DICE:
				last_seen = DICE_SEEN_DICE;
				/* If we see a 'd' without a number before it, we assume it
				 * to be one die. */
				if (strlen(token) == 0) {
					token[0] = '1';
					token[1] = '\0';
				}
				break;

			case DICE_STATE_FLUSH_SIDE:
				last_seen = DICE_SEEN_SIDE;
				break;

			case DICE_STATE_FLUSH_BONUS:
				last_seen = DICE_SEEN_BONUS;
				break;

			case DICE_STATE_FLUSH_ALL:
				/* Flushing all means that we are flushing whatever comes after
				 * it was that we last saw. */
				if (last_seen < DICE_SEEN_BONUS)
					last_seen++;
				break;

			case DICE_STATE_BONUS:
				/* The bonus state is weird, so if we last saw dice, we're now
				 * seeing sides. */
				if (last_seen == DICE_SEEN_DICE)
					last_seen = DICE_SEEN_SIDE;
				else
					last_seen = DICE_SEEN_BONUS;
				break;

			default:
				/* We're in a state that shouldn't flush anything. */
				flush = FALSE;
				break;
		}

		/*
		 * If we have a token that we need to flush, put it where it needs to
		 * go in the dice object. If the token is an uppercase letter, it's
		 * a variable and needs to go in the expression table. Otherwise, we
		 * try to parse it as a number, where it is set directly as a value.
		 */
		if (flush && strlen(token) > 0) {
			int value = 0;
			bool is_variable = FALSE;

			if (isupper(token[0])) {
				value = dice_add_variable(dice, token);
				is_variable = TRUE;
			}
			else {
				value = (int)strtol(token, NULL, 0);
				is_variable = FALSE;
			}

			switch (last_seen) {
				case DICE_SEEN_BASE:
					dice->b = value;
					dice->ex_b = is_variable;
					break;
				case DICE_SEEN_DICE:
					dice->x = value;
					dice->ex_x = is_variable;
					break;
				case DICE_SEEN_SIDE:
					dice->y = value;
					dice->ex_y = is_variable;
					break;
				case DICE_SEEN_BONUS:
					dice->m = value;
					dice->ex_m = is_variable;
					break;
				default:
					break;
			}

			memset(token, 0, DICE_TOKEN_SIZE + 1);
			token_end = 0;
		}
	}

	return TRUE;
}
/**
 * Parse a formatted string for values and variables to represent a dice roll.
 *
 * This function can parse a number of formats in the general style of "1+2d3M4"
 * (base, dice, sides, and bonus). Varibles (to which expressions can be bound)
 * can be subsitituted for numeric values by using an all-uppercase name
 * starting with $.
 * Spaces are ignored, concatenating the strings on either side of the space
 * character. Tokens (numbers and variable names) longer than the maximum will
 * be truncated. The unit test demonstrates the variety of valid strings.
 *
 * \param dice is the dice object to parse the string into.
 * \param string is the string to be parsed.
 * \return TRUE if parsing was successful, FALSE if not.
 */
Parse a formatted string for values and variables to represent a dice roll.
This function can parse a number of formats in the general style of "1+2d3M4"
(base, dice, sides, and bonus). Varibles (to which expressions can be bound)
can be subsitituted for numeric values by using an all-uppercase name
starting with $.
Spaces are ignored, concatenating the strings on either side of the space
character. Tokens (numbers and variable names) longer than the maximum will
be truncated. The unit test demonstrates the variety of valid strings.

\param dice is the dice object to parse the string into.
\param string is the string to be parsed.
\return TRUE if parsing was successful, FALSE if not.
Parse a formatted string for values and variables to represent a dice roll . This function can parse a number of formats in the general style of " 1 + 2d3M4 " ( base dice sides and bonus ) . Varibles ( to which expressions can be bound ) can be subsitituted for numeric values by using an all - uppercase name starting with $ . Spaces are ignored concatenating the strings on either side of the space character . Tokens ( numbers and variable names ) longer than the maximum will be truncated . The unit test demonstrates the variety of valid strings . \ param dice is the dice object to parse the string into . \ param string is the string to be parsed . \ return TRUE if parsing was successful FALSE if not .
bool dice_parse_string(dice_t *dice, const char *string)
{
	char token[DICE_TOKEN_SIZE + 1] = { '\0' };
	size_t token_end = 0;
	size_t current = 0;
	dice_state_t state = 0;
	enum last_seen_e {
		DICE_SEEN_NONE,
		DICE_SEEN_BASE,
		DICE_SEEN_DICE,
		DICE_SEEN_SIDE,
		DICE_SEEN_BONUS,
	} last_seen = DICE_SEEN_NONE;
	if (dice == NULL || string == NULL)
		return FALSE;
	dice_reset(dice);
	for (current = 0; current <= strlen(string); current++) {
		bool flush;
		dice_input_t input_type = DICE_INPUT_MAX;
		if (isspace(string[current]))
			continue;
		input_type = dice_input_for_char(string[current]);
		switch (input_type) {
			case DICE_INPUT_AMP:
			case DICE_INPUT_BASE:
			case DICE_INPUT_DICE:
			case DICE_INPUT_VAR:
			case DICE_INPUT_NULL:
				state = dice_parse_state_transition(state, input_type);
				break;
			case DICE_INPUT_MINUS:
			case DICE_INPUT_DIGIT:
			case DICE_INPUT_UPPER:
				if (token_end < DICE_TOKEN_SIZE) {
					token[token_end] = string[current];
					token_end++;
				}
				state = dice_parse_state_transition(state, input_type);
				break;
			default:
				break;
		}
		if (string[current] == 'M') {
			if (state == DICE_STATE_VAR || state == DICE_STATE_VAR_CHAR) {
				if (token_end < DICE_TOKEN_SIZE) {
					token[token_end] = string[current];
					token_end++;
				}
				state = dice_parse_state_transition(state, DICE_INPUT_UPPER);
			}
			else
				state = dice_parse_state_transition(state, DICE_INPUT_BONUS);
		}
		else if (string[current] == 'm') {
			state = dice_parse_state_transition(state, DICE_INPUT_BONUS);
		}
		if (state >= DICE_STATE_MAX)
			return FALSE;
		flush = TRUE;
		switch (state) {
			case DICE_STATE_FLUSH_BASE:
				last_seen = DICE_SEEN_BASE;
				break;
			case DICE_STATE_FLUSH_DICE:
				last_seen = DICE_SEEN_DICE;
				if (strlen(token) == 0) {
					token[0] = '1';
					token[1] = '\0';
				}
				break;
			case DICE_STATE_FLUSH_SIDE:
				last_seen = DICE_SEEN_SIDE;
				break;
			case DICE_STATE_FLUSH_BONUS:
				last_seen = DICE_SEEN_BONUS;
				break;
			case DICE_STATE_FLUSH_ALL:
				if (last_seen < DICE_SEEN_BONUS)
					last_seen++;
				break;
			case DICE_STATE_BONUS:
				if (last_seen == DICE_SEEN_DICE)
					last_seen = DICE_SEEN_SIDE;
				else
					last_seen = DICE_SEEN_BONUS;
				break;
			default:
				flush = FALSE;
				break;
		}
		if (flush && strlen(token) > 0) {
			int value = 0;
			bool is_variable = FALSE;
			if (isupper(token[0])) {
				value = dice_add_variable(dice, token);
				is_variable = TRUE;
			}
			else {
				value = (int)strtol(token, NULL, 0);
				is_variable = FALSE;
			}
			switch (last_seen) {
				case DICE_SEEN_BASE:
					dice->b = value;
					dice->ex_b = is_variable;
					break;
				case DICE_SEEN_DICE:
					dice->x = value;
					dice->ex_x = is_variable;
					break;
				case DICE_SEEN_SIDE:
					dice->y = value;
					dice->ex_y = is_variable;
					break;
				case DICE_SEEN_BONUS:
					dice->m = value;
					dice->ex_m = is_variable;
					break;
				default:
					break;
			}
			memset(token, 0, DICE_TOKEN_SIZE + 1);
			token_end = 0;
		}
	}
	return TRUE;
}
bool dice_parse_string ( dice_t * dice , const char * string ) { char token [ DICE_TOKEN_SIZE + 1 ] = { ' \0 ' } ; size_t token_end = 0 ; size_t current = 0 ; dice_state_t state = 0 ; enum last_seen_e { DICE_SEEN_NONE , DICE_SEEN_BASE , DICE_SEEN_DICE , DICE_SEEN_SIDE , DICE_SEEN_BONUS , } last_seen = DICE_SEEN_NONE ; if ( dice == NULL || string == NULL ) return FALSE ; dice_reset ( dice ) ; for ( current = 0 ; current <= strlen ( string ) ; current ++ ) { bool flush ; dice_input_t input_type = DICE_INPUT_MAX ; if ( isspace ( string [ current ] ) ) continue ; input_type = dice_input_for_char ( string [ current ] ) ; switch ( input_type ) { case DICE_INPUT_AMP : case DICE_INPUT_BASE : case DICE_INPUT_DICE : case DICE_INPUT_VAR : case DICE_INPUT_NULL : state = dice_parse_state_transition ( state , input_type ) ; break ; case DICE_INPUT_MINUS : case DICE_INPUT_DIGIT : case DICE_INPUT_UPPER : if ( token_end < DICE_TOKEN_SIZE ) { token [ token_end ] = string [ current ] ; token_end ++ ; } state = dice_parse_state_transition ( state , input_type ) ; break ; default : break ; } if ( string [ current ] == ' ' ) { if ( state == DICE_STATE_VAR || state == DICE_STATE_VAR_CHAR ) { if ( token_end < DICE_TOKEN_SIZE ) { token [ token_end ] = string [ current ] ; token_end ++ ; } state = dice_parse_state_transition ( state , DICE_INPUT_UPPER ) ; } else state = dice_parse_state_transition ( state , DICE_INPUT_BONUS ) ; } else if ( string [ current ] == ' ' ) { state = dice_parse_state_transition ( state , DICE_INPUT_BONUS ) ; } if ( state >= DICE_STATE_MAX ) return FALSE ; flush = TRUE ; switch ( state ) { case DICE_STATE_FLUSH_BASE : last_seen = DICE_SEEN_BASE ; break ; case DICE_STATE_FLUSH_DICE : last_seen = DICE_SEEN_DICE ; if ( strlen ( token ) == 0 ) { token [ 0 ] = ' ' ; token [ 1 ] = ' \0 ' ; } break ; case DICE_STATE_FLUSH_SIDE : last_seen = DICE_SEEN_SIDE ; break ; case DICE_STATE_FLUSH_BONUS : last_seen = DICE_SEEN_BONUS ; break ; case DICE_STATE_FLUSH_ALL : if ( last_seen < DICE_SEEN_BONUS ) last_seen ++ ; break ; case DICE_STATE_BONUS : if ( last_seen == DICE_SEEN_DICE ) last_seen = DICE_SEEN_SIDE ; else last_seen = DICE_SEEN_BONUS ; break ; default : flush = FALSE ; break ; } if ( flush && strlen ( token ) > 0 ) { int value = 0 ; bool is_variable = FALSE ; if ( isupper ( token [ 0 ] ) ) { value = dice_add_variable ( dice , token ) ; is_variable = TRUE ; } else { value = ( int ) strtol ( token , NULL , 0 ) ; is_variable = FALSE ; } switch ( last_seen ) { case DICE_SEEN_BASE : dice -> b = value ; dice -> ex_b = is_variable ; break ; case DICE_SEEN_DICE : dice -> x = value ; dice -> ex_x = is_variable ; break ; case DICE_SEEN_SIDE : dice -> y = value ; dice -> ex_y = is_variable ; break ; case DICE_SEEN_BONUS : dice -> m = value ; dice -> ex_m = is_variable ; break ; default : break ; } memset ( token , 0 , DICE_TOKEN_SIZE + 1 ) ; token_end = 0 ; } } return TRUE ; }
Parse a formatted string for values and variables to represent a dice roll.
Parse a formatted string for values and variables to represent a dice roll .
/* We need to keep track of the last thing we saw, since the parser isn't complex. */ /* Reset all internal state, since this object might be reused. */ /* Note that we are including the string terminator as part of the parse. */ /* Skip spaces; this will concatenate digits and variable names. */ /*
		 * Get the next state, based on the type of input char. If it's a
		 * possible number or varible name, we'll store the character in the
		 * token buffer.
		 */ /* Truncate tokens if they are too long to fit. */ /*
		 * Allow 'M' to be used as the bonus marker and to be used in variable
		 * names.
		 * Ideally, 'm' should be the only marker and this could go away by
		 * adding a case to the switch above for DICE_INPUT_BONUS
		 * (underneath DICE_INPUT_NULL).
		 */ /* Illegal transition. */ /*
		 * Default flushing to true, since there are more states that don't
		 * need to be flushed. For some states, we need to do a bit of extra
		 * work, since the parser isn't that complex. A more complex parser
		 * would have more explicit states for variable names.
		 */ /* If we see a 'd' without a number before it, we assume it
				 * to be one die. */ /* Flushing all means that we are flushing whatever comes after
				 * it was that we last saw. */ /* The bonus state is weird, so if we last saw dice, we're now
				 * seeing sides. */ /* We're in a state that shouldn't flush anything. */ /*
		 * If we have a token that we need to flush, put it where it needs to
		 * go in the dice object. If the token is an uppercase letter, it's
		 * a variable and needs to go in the expression table. Otherwise, we
		 * try to parse it as a number, where it is set directly as a value.
		 */