From b1cd9bcfef32668c5daf3b9a3d3b4da1e83781d7 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 30 Sep 2018 19:47:34 +0200 Subject: [PATCH] Pretty good progress on the OrderParser implementaiton. Still finding bugs by unit-testing, though. --- .gitignore | 1 + src/test_eressea.c | 1 + src/util/CMakeLists.txt | 2 +- src/util/order_parser.c | 199 +++++++++++++++++++++++++++++++++-- src/util/order_parser.h | 13 +-- src/util/order_parser.test.c | 65 ++++++++++++ 6 files changed, 263 insertions(+), 18 deletions(-) create mode 100644 src/util/order_parser.test.c diff --git a/.gitignore b/.gitignore index d5e8e1b10..3dcf24224 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ Thumbs.db *.cfg *.cmd tmp/ +tests/orders.txt tests/config.lua tests/reports/ tests/data/185.dat diff --git a/src/test_eressea.c b/src/test_eressea.c index 439fee1d6..9897c4bf0 100644 --- a/src/test_eressea.c +++ b/src/test_eressea.c @@ -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); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index b4d067f57..362ee4fb2 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -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 diff --git a/src/util/order_parser.c b/src/util/order_parser.c index b0d6d2d10..ec40f5615 100644 --- a/src/util/order_parser.c +++ b/src/util/order_parser.c @@ -4,7 +4,10 @@ #include "order_parser.h" +#include +#include #include +#include 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); +} diff --git a/src/util/order_parser.h b/src/util/order_parser.h index c8f20e89e..10a258875 100644 --- a/src/util/order_parser.h +++ b/src/util/order_parser.h @@ -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 diff --git a/src/util/order_parser.test.c b/src/util/order_parser.test.c new file mode 100644 index 000000000..a4bc58e97 --- /dev/null +++ b/src/util/order_parser.test.c @@ -0,0 +1,65 @@ +#ifdef _MSC_VER +#include +#endif + +#include "order_parser.h" +#include "strings.h" + +#include + +#include + +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; +}