Pretty good progress on the OrderParser implementaiton.

Still finding bugs by unit-testing, though.
This commit is contained in:
Enno Rehling 2018-09-30 19:47:34 +02:00
parent 0c4d1ec2b2
commit b1cd9bcfef
6 changed files with 263 additions and 18 deletions

1
.gitignore vendored
View File

@ -36,6 +36,7 @@ Thumbs.db
*.cfg
*.cmd
tmp/
tests/orders.txt
tests/config.lua
tests/reports/
tests/data/185.dat

View File

@ -81,6 +81,7 @@ int RunAllTests(int argc, char *argv[])
ADD_SUITE(functions);
ADD_SUITE(gamedata);
ADD_SUITE(language);
ADD_SUITE(order_parser);
ADD_SUITE(parser);
ADD_SUITE(password);
ADD_SUITE(umlaut);

View File

@ -16,7 +16,7 @@ language.test.c
# log.test.c
message.test.c
# nrmessage.test.c
# order_parser.test.c
order_parser.test.c
# param.test.c
parser.test.c
password.test.c

View File

@ -4,7 +4,10 @@
#include "order_parser.h"
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
struct OrderParserStruct {
void *m_userData;
@ -18,34 +21,208 @@ struct OrderParserStruct {
int m_lineNumber;
};
void OP_SetUnitHandler(OP_Parser op, OP_UnitHandler handler)
void OP_SetUnitHandler(OP_Parser parser, OP_UnitHandler handler)
{
op->m_unitHandler = handler;
parser->m_unitHandler = handler;
}
void OP_SetFactionHandler(OP_Parser op, OP_FactionHandler handler) {
op->m_factionHandler = handler;
void OP_SetFactionHandler(OP_Parser parser, OP_FactionHandler handler) {
parser->m_factionHandler = handler;
}
void OP_SetOrderHandler(OP_Parser op, OP_OrderHandler handler) {
op->m_orderHandler = handler;
void OP_SetOrderHandler(OP_Parser parser, OP_OrderHandler handler) {
parser->m_orderHandler = handler;
}
void OP_SetUserData(OP_Parser op, void *userData) {
op->m_userData = userData;
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_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 op) {
free(op);
void OP_ParserFree(OP_Parser parser) {
free(parser->m_buffer);
free(parser);
}
enum OP_Status OP_Parse(OP_Parser op, const char *s, int len, int isFinal)
static enum OP_Error buffer_append(OP_Parser parser, const char *s, int len)
{
if (parser->m_buffer == NULL) {
parser->m_buffer = malloc(len + 1);
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 {
size_t total = len;
char * buffer;
total += (parser->m_bufferEnd - parser->m_bufferPtr);
/* TODO: recycle buffers, reduce mallocs. */
buffer = malloc(total + 1);
memcpy(buffer, parser->m_bufferPtr, total - len);
memcpy(buffer + total - len, s, len);
buffer[total] = '\0';
free(parser->m_buffer);
parser->m_buffer = buffer;
if (!parser->m_buffer) {
return OP_ERROR_NO_MEMORY;
}
parser->m_bufferPtr = parser->m_buffer;
parser->m_bufferEnd = parser->m_buffer + total;
}
return OP_ERROR_NONE;
}
static enum OP_Error handle_line(OP_Parser parser) {
if (parser->m_orderHandler) {
parser->m_orderHandler(parser->m_userData, parser->m_bufferPtr);
}
return OP_ERROR_NONE;
}
static char *skip_spaces(char *pos) {
char *next;
for (next = pos; *next && *next != '\n'; ++next) {
/* TODO: handle unicode whitespace */
if (!isspace(*next)) break;
}
return next;
}
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;
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 */
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: */
pos = next + 1;
++parser->m_lineNumber;
break;
}
else {
/* is this backslash the final character? */
next = skip_spaces(pos + 1);
if (*next == '\n') {
/* we have a multi-line comment! */
pos = next + 1;
++parser->m_lineNumber;
}
else {
/* keep looking for a backslash */
pos = next;
}
}
}
} while (next && *next);
if (next && pos < parser->m_bufferEnd) {
/* we skip the comment, and there is more data in the buffer */
parser->m_bufferPtr = pos;
}
else {
/* we exhausted the buffer before we got to the end of the comment */
if (isFinal) {
/* the input ended on this comment line, which is fine */
return OP_STATUS_OK;
}
else {
/* skip what we have of the comment, keep the semicolon, keep going */
ptrdiff_t skip = parser->m_bufferEnd - parser->m_bufferPtr;
if (skip > 1) {
parser->m_bufferPtr += (skip - 1);
parser->m_bufferPtr[0] = ';';
}
}
}
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;
if (parser->m_bufferPtr >= parser->m_bufferEnd) {
buffer_free(parser);
}
code = buffer_append(parser, s, len);
if (code != OP_ERROR_NONE) {
parser->m_errorCode = code;
return OP_STATUS_ERROR;
}
return parse_buffer(parser, isFinal);
}

View File

@ -34,11 +34,12 @@ typedef void(*OP_UnitHandler) (void *userData, int no);
typedef void(*OP_OrderHandler) (void *userData, const char *str);
OP_Parser OP_ParserCreate(void);
void OP_ParserFree(OP_Parser op);
enum OP_Status OP_Parse(OP_Parser op, const char *s, int len, int isFinal);
void OP_SetUnitHandler(OP_Parser op, OP_UnitHandler handler);
void OP_SetFactionHandler(OP_Parser op, OP_FactionHandler handler);
void OP_SetOrderHandler(OP_Parser op, OP_OrderHandler handler);
void OP_SetUserData(OP_Parser op, void *userData);
void OP_ParserFree(OP_Parser parser);
void OP_ParserReset(OP_Parser parser);
enum OP_Status OP_Parse(OP_Parser parser, const char *s, int len, int isFinal);
void OP_SetUnitHandler(OP_Parser parser, OP_UnitHandler handler);
void OP_SetFactionHandler(OP_Parser parser, OP_FactionHandler handler);
void OP_SetOrderHandler(OP_Parser parser, OP_OrderHandler handler);
void OP_SetUserData(OP_Parser parser, void *userData);
#endif

View File

@ -0,0 +1,65 @@
#ifdef _MSC_VER
#include <platform.h>
#endif
#include "order_parser.h"
#include "strings.h"
#include <CuTest.h>
#include <string.h>
static void test_parse_noop(CuTest *tc) {
OP_Parser parser;
parser = OP_ParserCreate();
CuAssertPtrNotNull(tc, parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello World", 11, 1));
OP_ParserFree(parser);
}
static void copy_line(void *udata, const char *str) {
char *dst = (char *)udata;
if (dst) {
strcpy(dst, str);
}
}
static void test_parse_orders(CuTest *tc) {
OP_Parser parser;
char lastline[1024];
parser = OP_ParserCreate();
OP_SetUserData(parser, lastline);
OP_SetOrderHandler(parser, copy_line);
CuAssertPtrNotNull(tc, parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello World", 11, 1));
CuAssertStrEquals(tc, "Hello World", lastline);
OP_ParserReset(parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello World\n", 12, 1));
CuAssertStrEquals(tc, "Hello World", lastline);
OP_ParserReset(parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello\\\n World", 13, 1));
CuAssertStrEquals(tc, "Hello World", lastline);
OP_ParserReset(parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello;World", 11, 1));
CuAssertStrEquals(tc, "Hello", lastline);
OP_ParserReset(parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello\\World", 11, 1));
CuAssertStrEquals(tc, "Hello\\World", lastline);
OP_ParserReset(parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello ", 6, 0));
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "World", 5, 1));
CuAssertStrEquals(tc, "Hello World", lastline);
OP_ParserReset(parser);
CuAssertIntEquals(tc, OP_STATUS_OK, OP_Parse(parser, "Hello\\World \\", 14, 1));
CuAssertStrEquals(tc, "Hello\\World ", lastline);
OP_ParserFree(parser);
}
CuSuite *get_order_parser_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_parse_noop);
SUITE_ADD_TEST(suite, test_parse_orders);
return suite;
}