server/src/util/order_parser.c

287 lines
9.2 KiB
C

#ifdef _MSC_VER
#include <platform.h>
#endif
#include "order_parser.h"
#include <assert.h>
#include <wctype.h>
#include <stdlib.h>
#include <string.h>
struct OrderParserStruct {
void *m_userData;
char *m_buffer;
char *m_bufferPtr;
size_t m_bufferSize;
const char *m_bufferEnd;
OP_OrderHandler m_orderHandler;
enum OP_Error m_errorCode;
int m_lineNumber;
};
enum OP_Error OP_GetErrorCode(OP_Parser parser) {
return parser->m_errorCode;
}
void OP_SetOrderHandler(OP_Parser parser, OP_OrderHandler handler) {
parser->m_orderHandler = handler;
}
void OP_SetUserData(OP_Parser parser, void *userData) {
parser->m_userData = userData;
}
static void buffer_free(OP_Parser parser)
{
/* TODO: recycle buffers, reduce mallocs. */
free(parser->m_buffer);
parser->m_bufferSize = 0;
parser->m_bufferEnd = parser->m_bufferPtr = parser->m_buffer = NULL;
}
void OP_ParserReset(OP_Parser parser) {
parser->m_lineNumber = 1;
buffer_free(parser);
}
OP_Parser OP_ParserCreate(void)
{
OP_Parser parser = calloc(1, sizeof(struct OrderParserStruct));
OP_ParserReset(parser);
return parser;
}
void OP_ParserFree(OP_Parser parser) {
free(parser->m_buffer);
free(parser);
}
static enum OP_Error buffer_append(OP_Parser parser, const char *s, int len)
{
size_t total = len + 1;
size_t remain = parser->m_bufferEnd - parser->m_bufferPtr;
total += remain;
if (remain > 0) {
/* there is remaining data in the buffer, should we move it to the front? */
if (total <= parser->m_bufferSize) {
/* reuse existing buffer */
memmove(parser->m_buffer, parser->m_bufferPtr, remain);
memcpy(parser->m_buffer + remain, s, len);
parser->m_buffer[total - 1] = '\0';
parser->m_bufferPtr = parser->m_buffer;
parser->m_bufferEnd = parser->m_bufferPtr + total - 1;
return OP_ERROR_NONE;
}
}
else if (parser->m_bufferPtr >= parser->m_bufferEnd) {
buffer_free(parser);
}
if (parser->m_buffer == NULL) {
parser->m_bufferSize = len + 1;
parser->m_buffer = malloc(parser->m_bufferSize);
if (!parser->m_buffer) {
return OP_ERROR_NO_MEMORY;
}
memcpy(parser->m_buffer, s, len);
parser->m_buffer[len] = '\0';
parser->m_bufferPtr = parser->m_buffer;
parser->m_bufferEnd = parser->m_buffer + len;
}
else {
char * buffer;
/* TODO: recycle buffers, reduce mallocs. */
if (parser->m_bufferSize < total) {
parser->m_bufferSize = total;
buffer = malloc(parser->m_bufferSize);
if (!buffer) {
return OP_ERROR_NO_MEMORY;
}
memcpy(buffer, parser->m_bufferPtr, total - len - 1);
memcpy(buffer + total - len - 1, s, len);
free(parser->m_buffer);
parser->m_buffer = buffer;
}
else {
memcpy(parser->m_buffer, parser->m_bufferPtr, total - len);
memcpy(parser->m_buffer + total - len, s, len);
}
parser->m_buffer[total - 1] = '\0';
parser->m_bufferPtr = parser->m_buffer;
parser->m_bufferEnd = parser->m_buffer + total - 1;
}
return OP_ERROR_NONE;
}
static char *skip_spaces(char *pos) {
char *next;
for (next = pos; *next && *next != '\n'; ++next) {
wint_t wch = *(unsigned char *)next;
/* TODO: handle unicode whitespace */
if (!iswspace(wch)) break;
}
return next;
}
static enum OP_Error handle_line(OP_Parser parser) {
if (parser->m_orderHandler) {
char * str = skip_spaces(parser->m_bufferPtr);
if (*str) {
parser->m_orderHandler(parser->m_userData, str);
}
}
return OP_ERROR_NONE;
}
static enum OP_Status parse_buffer(OP_Parser parser, int isFinal)
{
char * pos = strpbrk(parser->m_bufferPtr, "\\;\n");
while (pos) {
enum OP_Error code;
size_t len = pos - parser->m_bufferPtr;
char *next;
int continue_comment = 0;
switch (*pos) {
case '\n':
*pos = '\0';
code = handle_line(parser);
++parser->m_lineNumber;
if (code != OP_ERROR_NONE) {
parser->m_errorCode = code;
return OP_STATUS_ERROR;
}
parser->m_bufferPtr = pos + 1;
pos = strpbrk(parser->m_bufferPtr, "\\;\n");
break;
case '\\':
/* if this is the last non-space before the line break, then lines need to be joined */
next = skip_spaces(pos + 1);
if (*next == '\n') {
ptrdiff_t shift = (next + 1 - pos);
assert(shift > 0);
memmove(parser->m_bufferPtr + shift, parser->m_bufferPtr, len);
parser->m_bufferPtr += shift;
pos = strpbrk(next + 1, "\\;\n");
++parser->m_lineNumber;
}
else {
/* this is not multi-line input yet, so do nothing */
if (pos[1] == '\0') {
/* end of available input */
if (isFinal) {
/* input ends on a pointless backslash, kill it */
pos[0] = '\0';
pos = NULL;
}
else {
/* backslash is followed by data that we do not know */
pos = NULL;
}
}
else {
pos = strpbrk(pos + 1, "\\;\n");
}
}
break;
case ';':
/* the current line ends in a comment */
*pos++ = '\0';
handle_line(parser);
/* find the end of the comment so we can skip it.
* obs: multi-line comments are possible with a backslash. */
do {
next = strpbrk(pos, "\\\n");
if (next) {
if (*next == '\n') {
/* no more lines in this comment, we're done: */
++parser->m_lineNumber;
break; /* exit loop */
}
else {
/* is this backslash the final character? */
next = skip_spaces(next + 1);
if (*next == '\n') {
/* we have a multi-line comment! */
pos = next + 1;
++parser->m_lineNumber;
}
else if (*next == '\0') {
/* cannot find the EOL char yet, stream is dry. keep ; and \ */
continue_comment = 2;
}
else {
/* keep looking for a backslash */
pos = next;
}
}
}
} while (next && *next);
if (!next) {
/* we exhausted the buffer before we finished the line */
if (isFinal) {
/* this comment was at the end of the file, it just has no newline. done! */
return OP_STATUS_OK;
}
else {
/* there is more of this line in the next buffer, save the semicolon */
continue_comment = 1;
}
}
else {
if (*next) {
/* end comment parsing, begin parsing a new line */
pos = next + 1;
continue_comment = 0;
}
else if (!continue_comment) {
/* reached end of input naturally, need more data to finish */
continue_comment = 1;
}
}
if (continue_comment) {
ptrdiff_t skip = parser->m_bufferEnd - parser->m_bufferPtr;
assert(skip >= continue_comment);
if (skip >= continue_comment) {
/* should always be true */
parser->m_bufferPtr += (skip - continue_comment);
parser->m_bufferPtr[0] = ';';
}
if (continue_comment == 2) {
parser->m_bufferPtr[1] = '\\';
}
continue_comment = 0;
return OP_STATUS_OK;
}
/* continue the outer loop */
parser->m_bufferPtr = pos;
pos = strpbrk(pos, "\\;\n");
break;
default:
parser->m_errorCode = OP_ERROR_SYNTAX;
return OP_STATUS_ERROR;
}
}
if (isFinal && parser->m_bufferPtr < parser->m_bufferEnd) {
/* this line ends without a line break */
handle_line(parser);
}
return OP_STATUS_OK;
}
enum OP_Status OP_Parse(OP_Parser parser, const char *s, int len, int isFinal)
{
enum OP_Error code;
code = buffer_append(parser, s, len);
if (code != OP_ERROR_NONE) {
parser->m_errorCode = code;
return OP_STATUS_ERROR;
}
return parse_buffer(parser, isFinal);
}