From 8ab120b87fc1ff4ac77d922cf786a044433f0de7 Mon Sep 17 00:00:00 2001 From: rexim Date: Tue, 28 Aug 2018 19:55:01 +0700 Subject: [PATCH] (#303) Implement read_expr_from_file --- CMakeLists.txt | 16 ++++++++++ src/script/parser.c | 65 ++++++++++++++++++++++++++++++++++++--- test-data/simple-sum.lisp | 1 + test/main.c | 55 ++------------------------------- test/parser_suite.h | 55 +++++++++++++++++++++++++++++++++ test/test.h | 40 ++++++++++++++++++++++++ test/tokenizer_suite.h | 58 ++++++++++++++++++++++++++++++++++ 7 files changed, 233 insertions(+), 57 deletions(-) create mode 100644 test-data/simple-sum.lisp create mode 100644 test/parser_suite.h create mode 100644 test/tokenizer_suite.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ceb7b618..100076f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,11 +93,26 @@ set(HEADER_FILES add_executable(nothing ${SOURCE_FILES} ${HEADER_FILES}) add_executable(nothing_test + src/script/expr.c + src/script/expr.h + src/script/parser.c + src/script/parser.h src/script/tokenizer.c src/script/tokenizer.h + src/system/lt.c + src/system/lt.h + src/system/error.c + src/system/error.h + src/system/lt/lt_adapters.c + src/system/lt/lt_adapters.h + src/system/lt/lt_slot.c + src/system/lt/lt_slot.h test/main.c + test/test.h + test/tokenizer_suite.h ) target_link_libraries(nothing ${SDL2_LIBRARY} ${SDL2_MIXER_LIBRARY}) +target_link_libraries(nothing_test ${SDL2_LIBRARY} ${SDL2_MIXER_LIBRARY}) if(("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "CLANG")) set(CMAKE_C_FLAGS @@ -137,3 +152,4 @@ endif() file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/sounds DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/fonts DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test-data DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/script/parser.c b/src/script/parser.c index cb4475fc..82b2246d 100644 --- a/src/script/parser.c +++ b/src/script/parser.c @@ -1,9 +1,16 @@ #include #include +#include +#include #include #include +#include #include "script/parser.h" +#include "system/lt.h" +#include "system/lt/lt_adapters.h" + +#define MAX_BUFFER_LENGTH (5 * 1000 * 1000) static struct ParseResult parse_expr(struct Token current_token); @@ -163,7 +170,52 @@ struct ParseResult read_expr_from_string(const char *str) struct ParseResult read_expr_from_file(const char *filename) { assert(filename); - return parse_failure("not implemented", NULL); + + Lt *lt = create_lt(); + if (lt == NULL) { + return parse_failure("Could not create Lt object", NULL); + } + + FILE *stream = PUSH_LT(lt, fopen(filename, "rb"), fclose_lt); + if (!stream) { + /* TODO: ParseResult should not be used for reporting IO failures */ + RETURN_LT(lt, parse_failure(strerror(errno), NULL)); + } + + if (fseek(stream, 0, SEEK_END) != 0) { + RETURN_LT(lt, parse_failure("Could not find the end of the file", NULL)); + } + + const long int buffer_length = ftell(stream); + + if (buffer_length < 0) { + RETURN_LT(lt, parse_failure("Couldn't get the size of file", NULL)); + } + + if (buffer_length == 0) { + RETURN_LT(lt, parse_failure("File is empty", NULL)); + } + + if (buffer_length >= MAX_BUFFER_LENGTH) { + RETURN_LT(lt, parse_failure("File is too big", NULL)); + } + + if (fseek(stream, 0, SEEK_SET) != 0) { + RETURN_LT(lt, parse_failure("Could not find the beginning of the file", NULL)); + } + + char * const buffer = PUSH_LT(lt, malloc((size_t) buffer_length + 1), free); + if (buffer == NULL) { + RETURN_LT(lt, parse_failure(strerror(errno), NULL)); + } + + if (fread(buffer, 1, (size_t) buffer_length, stream) != (size_t) buffer_length) { + RETURN_LT(lt, parse_failure("Could not read the file", NULL)); + } + + struct ParseResult result = read_expr_from_string(buffer); + + RETURN_LT(lt, result); } struct ParseResult parse_success(struct Expr expr, @@ -199,10 +251,13 @@ void print_parse_error(FILE *stream, return; } - fprintf(stream, "%s\n", str); - for (size_t i = 0; i < (size_t) (result.end - str); ++i) { - fprintf(stream, " "); + if (result.end) { + fprintf(stream, "%s\n", str); + for (size_t i = 0; i < (size_t) (result.end - str); ++i) { + fprintf(stream, " "); + } + fprintf(stream, "^\n"); } - fprintf(stream, "^\n"); + fprintf(stream, "%s\n", result.error_message); } diff --git a/test-data/simple-sum.lisp b/test-data/simple-sum.lisp new file mode 100644 index 00000000..1a9372c5 --- /dev/null +++ b/test-data/simple-sum.lisp @@ -0,0 +1 @@ +(+ 1 2 3) diff --git a/test/main.c b/test/main.c index f3362d01..26ceb159 100644 --- a/test/main.c +++ b/test/main.c @@ -1,59 +1,10 @@ -#include -#include -#include - #include "test.h" -#include "script/tokenizer.h" - -TEST(tokenizer_number_list_test) -{ - struct Token token = next_token("(1 2 3)"); - ASSERT_STREQN("(", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN("1", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN("2", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN("3", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN(")", token.begin, (size_t) (token.end - token.begin)); - - return 0; -} - -TEST(tokenizer_string_list_test) -{ - struct Token token = next_token("(\"foo\" \"bar\" \"baz\")"); - ASSERT_STREQN("(", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN("\"foo\"", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN("\"bar\"", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN("\"baz\"", token.begin, (size_t) (token.end - token.begin)); - - token = next_token(token.end); - ASSERT_STREQN(")", token.begin, (size_t) (token.end - token.begin)); - - return 0; -} - -TEST_SUITE(tokenizer_suite) -{ - TEST_RUN(tokenizer_number_list_test); - TEST_RUN(tokenizer_string_list_test); - return 0; -} +#include "tokenizer_suite.h" +#include "parser_suite.h" TEST_MAIN() { TEST_RUN(tokenizer_suite); + TEST_RUN(parser_suite); return 0; } diff --git a/test/parser_suite.h b/test/parser_suite.h new file mode 100644 index 00000000..13805a7b --- /dev/null +++ b/test/parser_suite.h @@ -0,0 +1,55 @@ +#ifndef PARSER_SUITE_H_ +#define PARSER_SUITE_H_ + +#include "test.h" +#include "script/parser.h" + +TEST(read_expr_from_file_test) +{ + struct ParseResult result = read_expr_from_file("test-data/simple-sum.lisp"); + + ASSERT_TRUE(!result.is_error, result.error_message); + + struct Expr head = result.expr; + + struct Expr expr = head; + ASSERT_INTEQ(EXPR_CONS, expr.type); + ASSERT_INTEQ(EXPR_ATOM, expr.cons->car.type); + ASSERT_INTEQ(ATOM_SYMBOL, expr.cons->car.atom->type); + ASSERT_STREQ("+", expr.cons->car.atom->sym); + + expr = expr.cons->cdr; + ASSERT_INTEQ(EXPR_CONS, expr.type); + ASSERT_INTEQ(EXPR_ATOM, expr.cons->car.type); + ASSERT_INTEQ(ATOM_NUMBER, expr.cons->car.atom->type); + ASSERT_FLOATEQ(1.0f, expr.cons->car.atom->num, 1e-3f); + + expr = expr.cons->cdr; + ASSERT_INTEQ(EXPR_CONS, expr.type); + ASSERT_INTEQ(EXPR_ATOM, expr.cons->car.type); + ASSERT_INTEQ(ATOM_NUMBER, expr.cons->car.atom->type); + ASSERT_FLOATEQ(2.0f, expr.cons->car.atom->num, 1e-3f); + + expr = expr.cons->cdr; + ASSERT_INTEQ(EXPR_CONS, expr.type); + ASSERT_INTEQ(EXPR_ATOM, expr.cons->car.type); + ASSERT_INTEQ(ATOM_NUMBER, expr.cons->car.atom->type); + ASSERT_FLOATEQ(3.0f, expr.cons->car.atom->num, 1e-3f); + + expr = expr.cons->cdr; + ASSERT_INTEQ(EXPR_ATOM, expr.type); + ASSERT_INTEQ(ATOM_SYMBOL, expr.atom->type); + ASSERT_STREQ("nil", expr.atom->sym); + + destroy_expr(head); + return 0; +} + +TEST_SUITE(parser_suite) +{ + TEST_RUN(read_expr_from_file_test); + + return 0; +} + +#endif // PARSER_SUITE_H_ diff --git a/test/test.h b/test/test.h index 732ef973..e86deeff 100644 --- a/test/test.h +++ b/test/test.h @@ -1,6 +1,8 @@ #ifndef TEST_H_ #define TEST_H_ +#include "math.h" + #define TEST_RUN(name) \ if (name() < 0) { \ return -1; \ @@ -20,6 +22,8 @@ } \ static int name##_body(void) +// TODO: ASSERT_* macros evaluate expressions several times + #define ASSERT_STREQN(expected, actual, n) \ if (strncmp(expected, actual, n) != 0) { \ fprintf(stderr, "\n%s:%d: ASSERT_STREQN: \n", \ @@ -33,6 +37,42 @@ return -1; \ } +#define ASSERT_STREQ(expected, actual) \ + if (strcmp(expected, actual) != 0) { \ + fprintf(stderr, "\n%s:%d: ASSERT_STREQ: \n", \ + __FILE__, __LINE__); \ + fprintf(stderr, " Expected: %s\n", expected); \ + fprintf(stderr, " Actual: %s\n", actual); \ + return -1; \ + } + +#define ASSERT_INTEQ(expected, actual) \ + if (expected != actual) { \ + fprintf(stderr, "\n%s:%d: ASSERT_INTEQ: \n", \ + __FILE__, __LINE__); \ + fprintf(stderr, " Expected: %d\n", expected); \ + fprintf(stderr, " Actual: %d\n", actual); \ + return -1; \ + } + +#define ASSERT_FLOATEQ(expected, actual, margin) \ + if (fabsf(expected - actual) > margin) { \ + fprintf(stderr, "\n%s:%d: ASSERT_INTEQ: \n", \ + __FILE__, __LINE__); \ + fprintf(stderr, " Expected: %f\n", expected); \ + fprintf(stderr, " Actual: %f\n", actual); \ + fprintf(stderr, " Margin: %f\n", margin); \ + return -1; \ + } + +#define ASSERT_TRUE(condition, message) \ + if (!condition) { \ + fprintf(stderr, "\n%s:%d: ASSERT_TRUE: false\n", \ + __FILE__, __LINE__); \ + fprintf(stderr, "%s\n", message); \ + return -1; \ + } + #define TEST_SUITE(name) \ static int name##_body(void); \ static int name(void) { \ diff --git a/test/tokenizer_suite.h b/test/tokenizer_suite.h new file mode 100644 index 00000000..22c039ff --- /dev/null +++ b/test/tokenizer_suite.h @@ -0,0 +1,58 @@ +#ifndef TOKENIZER_SUITE_H_ +#define TOKENIZER_SUITE_H_ + +#include +#include +#include + +#include "test.h" +#include "script/tokenizer.h" + +TEST(tokenizer_number_list_test) +{ + struct Token token = next_token("(1 2 3)"); + ASSERT_STREQN("(", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN("1", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN("2", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN("3", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN(")", token.begin, (size_t) (token.end - token.begin)); + + return 0; +} + +TEST(tokenizer_string_list_test) +{ + struct Token token = next_token("(\"foo\" \"bar\" \"baz\")"); + ASSERT_STREQN("(", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN("\"foo\"", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN("\"bar\"", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN("\"baz\"", token.begin, (size_t) (token.end - token.begin)); + + token = next_token(token.end); + ASSERT_STREQN(")", token.begin, (size_t) (token.end - token.begin)); + + return 0; +} + +TEST_SUITE(tokenizer_suite) +{ + TEST_RUN(tokenizer_number_list_test); + TEST_RUN(tokenizer_string_list_test); + return 0; +} + +#endif // TOKENIZER_SUITE_H_