429 lines
7.1 KiB
C
429 lines
7.1 KiB
C
/**
|
|
* @file reps.c
|
|
* @brief Bounded parser for Trainlog repetition-set shorthand.
|
|
*/
|
|
|
|
#include "trainlog/reps.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define TRAINLOG_REPS_TEXT_MAX 511U
|
|
#define TRAINLOG_REPS_VALUE_MAX 10000
|
|
|
|
static bool parse_token(
|
|
const char *start,
|
|
size_t length,
|
|
int *output
|
|
)
|
|
{
|
|
char buffer[32];
|
|
char *end = NULL;
|
|
long value;
|
|
|
|
if (
|
|
start == NULL ||
|
|
output == NULL ||
|
|
length == 0U ||
|
|
length >= sizeof(buffer)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
(void)memcpy(
|
|
buffer,
|
|
start,
|
|
length
|
|
);
|
|
|
|
buffer[length] = '\0';
|
|
|
|
value =
|
|
strtol(
|
|
buffer,
|
|
&end,
|
|
10
|
|
);
|
|
|
|
if (
|
|
end == buffer ||
|
|
*end != '\0' ||
|
|
value < 0L ||
|
|
value > TRAINLOG_REPS_VALUE_MAX
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
*output = (int)value;
|
|
return true;
|
|
}
|
|
|
|
static bool compact_text(
|
|
const char *text,
|
|
char output[TRAINLOG_REPS_TEXT_MAX + 1U]
|
|
)
|
|
{
|
|
size_t read_index;
|
|
size_t write_index = 0U;
|
|
|
|
if (
|
|
text == NULL ||
|
|
output == NULL
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
for (
|
|
read_index = 0U;
|
|
text[read_index] != '\0';
|
|
++read_index
|
|
) {
|
|
unsigned char value =
|
|
(unsigned char)text[read_index];
|
|
|
|
if (isspace(value) != 0) {
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
write_index >=
|
|
TRAINLOG_REPS_TEXT_MAX
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
output[write_index] =
|
|
(char)value;
|
|
|
|
++write_index;
|
|
}
|
|
|
|
output[write_index] = '\0';
|
|
|
|
return write_index > 0U;
|
|
}
|
|
|
|
static TrainlogStatus parse_repeat(
|
|
char *text,
|
|
int *output,
|
|
size_t capacity,
|
|
size_t *output_count
|
|
)
|
|
{
|
|
char *separator =
|
|
strchr(
|
|
text,
|
|
'x'
|
|
);
|
|
|
|
int count;
|
|
int reps;
|
|
size_t index;
|
|
|
|
if (separator == NULL) {
|
|
separator =
|
|
strchr(
|
|
text,
|
|
'X'
|
|
);
|
|
}
|
|
|
|
if (separator == NULL) {
|
|
return TRAINLOG_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
if (
|
|
strchr(
|
|
separator + 1,
|
|
'x'
|
|
) != NULL ||
|
|
strchr(
|
|
separator + 1,
|
|
'X'
|
|
) != NULL
|
|
) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*separator = '\0';
|
|
|
|
if (
|
|
!parse_token(
|
|
text,
|
|
strlen(text),
|
|
&count
|
|
) ||
|
|
!parse_token(
|
|
separator + 1,
|
|
strlen(separator + 1),
|
|
&reps
|
|
) ||
|
|
count < 1 ||
|
|
(size_t)count > capacity
|
|
) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
for (
|
|
index = 0U;
|
|
index < (size_t)count;
|
|
++index
|
|
) {
|
|
output[index] = reps;
|
|
}
|
|
|
|
*output_count = (size_t)count;
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
static TrainlogStatus parse_pyramid(
|
|
char *text,
|
|
int *output,
|
|
size_t capacity,
|
|
size_t *output_count
|
|
)
|
|
{
|
|
char *first =
|
|
strstr(
|
|
text,
|
|
".."
|
|
);
|
|
|
|
char *second;
|
|
int start;
|
|
int peak;
|
|
int end;
|
|
size_t count = 0U;
|
|
int value;
|
|
|
|
if (first == NULL) {
|
|
return TRAINLOG_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
second =
|
|
strstr(
|
|
first + 2,
|
|
".."
|
|
);
|
|
|
|
if (
|
|
second == NULL ||
|
|
strstr(
|
|
second + 2,
|
|
".."
|
|
) != NULL
|
|
) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*first = '\0';
|
|
*second = '\0';
|
|
|
|
if (
|
|
!parse_token(
|
|
text,
|
|
strlen(text),
|
|
&start
|
|
) ||
|
|
!parse_token(
|
|
first + 2,
|
|
strlen(first + 2),
|
|
&peak
|
|
) ||
|
|
!parse_token(
|
|
second + 2,
|
|
strlen(second + 2),
|
|
&end
|
|
) ||
|
|
start > peak ||
|
|
end > peak
|
|
) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
for (
|
|
value = start;
|
|
value <= peak;
|
|
++value
|
|
) {
|
|
if (count >= capacity) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
output[count] = value;
|
|
++count;
|
|
}
|
|
|
|
if (peak > end) {
|
|
for (
|
|
value = peak - 1;
|
|
value >= end;
|
|
--value
|
|
) {
|
|
if (count >= capacity) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
output[count] = value;
|
|
++count;
|
|
}
|
|
}
|
|
|
|
if (count == 0U) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_count = count;
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
static TrainlogStatus parse_explicit(
|
|
const char *text,
|
|
int *output,
|
|
size_t capacity,
|
|
size_t *output_count
|
|
)
|
|
{
|
|
const char *cursor = text;
|
|
const char *token_start = text;
|
|
size_t count = 0U;
|
|
|
|
for (;;) {
|
|
if (
|
|
*cursor == ',' ||
|
|
*cursor == ';' ||
|
|
*cursor == '\0'
|
|
) {
|
|
int reps;
|
|
|
|
if (
|
|
count >= capacity ||
|
|
!parse_token(
|
|
token_start,
|
|
(size_t)(
|
|
cursor -
|
|
token_start
|
|
),
|
|
&reps
|
|
)
|
|
) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
output[count] = reps;
|
|
++count;
|
|
|
|
if (*cursor == '\0') {
|
|
break;
|
|
}
|
|
|
|
token_start =
|
|
cursor + 1;
|
|
}
|
|
|
|
++cursor;
|
|
}
|
|
|
|
if (count == 0U) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_count = count;
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
TrainlogStatus trainlog_reps_parse_sequence(
|
|
const char *text,
|
|
int *output,
|
|
size_t capacity,
|
|
size_t *output_count
|
|
)
|
|
{
|
|
char compact[
|
|
TRAINLOG_REPS_TEXT_MAX + 1U
|
|
];
|
|
|
|
char working[
|
|
TRAINLOG_REPS_TEXT_MAX + 1U
|
|
];
|
|
|
|
TrainlogStatus status;
|
|
|
|
if (
|
|
text == NULL ||
|
|
output_count == NULL ||
|
|
capacity == 0U ||
|
|
output == NULL
|
|
) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_count = 0U;
|
|
|
|
if (
|
|
!compact_text(
|
|
text,
|
|
compact
|
|
)
|
|
) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
(void)snprintf(
|
|
working,
|
|
sizeof(working),
|
|
"%s",
|
|
compact
|
|
);
|
|
|
|
status =
|
|
parse_repeat(
|
|
working,
|
|
output,
|
|
capacity,
|
|
output_count
|
|
);
|
|
|
|
if (
|
|
status !=
|
|
TRAINLOG_STATUS_NOT_FOUND
|
|
) {
|
|
return status;
|
|
}
|
|
|
|
(void)snprintf(
|
|
working,
|
|
sizeof(working),
|
|
"%s",
|
|
compact
|
|
);
|
|
|
|
status =
|
|
parse_pyramid(
|
|
working,
|
|
output,
|
|
capacity,
|
|
output_count
|
|
);
|
|
|
|
if (
|
|
status !=
|
|
TRAINLOG_STATUS_NOT_FOUND
|
|
) {
|
|
return status;
|
|
}
|
|
|
|
return
|
|
parse_explicit(
|
|
compact,
|
|
output,
|
|
capacity,
|
|
output_count
|
|
);
|
|
}
|