Extract line reading logic to reader and remove REPL tests

Reading a line into the buffer was the only logic complex enough to be
worth testing really, and exposing the necessary parts of it to test
it effectively was a pain.  Makes more sense to move read_line out and
throw away most of the tests.
This commit is contained in:
Camden Dixie O'Brien 2024-10-24 21:46:59 +01:00
parent ecd50c76c5
commit acb4cd38d7
9 changed files with 81 additions and 192 deletions

View File

@ -1,21 +1,9 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "evaluator.h"
#include "reader.h"
#include "repl.h" #include "repl.h"
static void print(const char *output, int len) static repl_t repl;
{
fwrite(output, 1, len, stdout);
}
static repl_t repl = {
.get_byte = getchar,
.read = read_expression,
.evaluate = evaluate,
.print = print,
};
int main(void) int main(void)
{ {

View File

@ -3,6 +3,8 @@
#include "memory_pool.h" #include "memory_pool.h"
int read_line(int (*get_byte)(void), char *buffer, int buffer_size);
const expression_t * const expression_t *
read_expression(memory_pool_t *pool, const char *input, int len); read_expression(memory_pool_t *pool, const char *input, int len);

View File

@ -3,23 +3,11 @@
#include "memory_pool.h" #include "memory_pool.h"
#define REPL_LINE_BUFFER_SIZE 128 #define REPL_BUFFER_SIZE 128
#define REPL_RESULT_BUFFER_SIZE 128
typedef int (*get_byte_fn_t)(void);
typedef const expression_t *(*read_fn_t)(
memory_pool_t *pool, const char *input, int len);
typedef int (*evaluate_fn_t)(const expression_t *expression);
typedef void (*print_fn_t)(const char *output, int len);
typedef struct { typedef struct {
get_byte_fn_t get_byte;
read_fn_t read;
evaluate_fn_t evaluate;
print_fn_t print;
memory_pool_t pool; memory_pool_t pool;
char line_buffer[REPL_LINE_BUFFER_SIZE]; char buffer[REPL_BUFFER_SIZE];
char result_buffer[REPL_RESULT_BUFFER_SIZE];
} repl_t; } repl_t;
bool step_repl(repl_t *repl); bool step_repl(repl_t *repl);

View File

@ -2,6 +2,7 @@
#include <ctype.h> #include <ctype.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h>
static int parse_expression( static int parse_expression(
memory_pool_t *pool, const char *input, int len, expression_t **out); memory_pool_t *pool, const char *input, int len, expression_t **out);
@ -152,6 +153,19 @@ static int parse_expression(
return used; return used;
} }
int read_line(int (*get_byte)(void), char *buffer, int buffer_size)
{
for (int len = 0; len < buffer_size; ++len) {
const int byte = get_byte();
if (EOF == byte)
return -1;
if ('\n' == byte)
return len;
buffer[len] = (char)byte;
}
return buffer_size;
}
const expression_t * const expression_t *
read_expression(memory_pool_t *pool, const char *input, int len) read_expression(memory_pool_t *pool, const char *input, int len)
{ {

View File

@ -1,5 +1,8 @@
#include "repl.h" #include "repl.h"
#include "evaluator.h"
#include "reader.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -7,26 +10,17 @@ bool step_repl(repl_t *repl)
{ {
init_memory_pool(&repl->pool); init_memory_pool(&repl->pool);
int len; const int len = read_line(getchar, repl->buffer, REPL_BUFFER_SIZE);
for (len = 0; len < REPL_LINE_BUFFER_SIZE; ++len) { if (len < 0)
const int byte = repl->get_byte();
if (EOF == byte)
return false; return false;
else if ('\n' == byte) const expression_t *e = read_expression(&repl->pool, repl->buffer, len);
break;
repl->line_buffer[len] = (char)byte;
}
const expression_t *e = repl->read(&repl->pool, repl->line_buffer, len);
if (NULL == e) { if (NULL == e) {
const char *msg = "Invalid expression\n"; puts("Invalid expression\n");
repl->print(msg, strlen(msg));
return true; return true;
} }
const int result = repl->evaluate(e); const int result = evaluate(e);
const int result_len = snprintf( printf("%d\n", result);
repl->result_buffer, REPL_RESULT_BUFFER_SIZE, "%d\n", result);
repl->print(repl->result_buffer, result_len);
return true; return true;
} }

View File

@ -22,9 +22,6 @@ clang $CFLAGS -o build/evaluator_tests \
clang $CFLAGS -Itests -c -o build/reader_tests.o tests/reader_tests.c clang $CFLAGS -Itests -c -o build/reader_tests.o tests/reader_tests.c
clang $CFLAGS -o build/reader_tests \ clang $CFLAGS -o build/reader_tests \
build/reader_tests.o build/lib.a build/testing.o build/reader_tests.o build/lib.a build/testing.o
clang $CFLAGS -Itests -c -o build/repl_tests.o tests/repl_tests.c
clang $CFLAGS -o build/repl_tests \
build/repl_tests.o build/lib.a build/testing.o
# Build application # Build application
clang $CFLAGS -c -o build/main.o app/main.c clang $CFLAGS -c -o build/main.o app/main.c

View File

@ -4,6 +4,5 @@ fails=0
build/evaluator_tests || fails=`expr $fails + 1` build/evaluator_tests || fails=`expr $fails + 1`
build/reader_tests || fails=`expr $fails + 1` build/reader_tests || fails=`expr $fails + 1`
build/repl_tests || fails=`expr $fails + 1`
if [ $fails -eq 0 ]; then echo Tests OK; fi if [ $fails -eq 0 ]; then echo Tests OK; fi

View File

@ -1,8 +1,21 @@
#include "reader.h" #include "reader.h"
#include "testing.h" #include "testing.h"
#include <stdio.h>
static memory_pool_t pool; static memory_pool_t pool;
static char *test_input;
static int test_input_len;
static int test_get_byte(void)
{
if (test_input_len < 0)
return EOF;
--test_input_len;
return *test_input++;
}
static void input_1234_is_read_as_number_with_expected_value(void) static void input_1234_is_read_as_number_with_expected_value(void)
{ {
init_memory_pool(&pool); init_memory_pool(&pool);
@ -234,6 +247,41 @@ static void trailing_spaces_are_ignored(void)
ASSERT_EQUAL(14, result->number); ASSERT_EQUAL(14, result->number);
} }
static void read_line_copies_line_into_buffer(void)
{
test_input = "foobar\nbazquux";
test_input_len = strlen(test_input);
char buffer[10];
read_line(test_get_byte, buffer, sizeof(buffer));
ASSERT_MEM_EQUAL("foobar", buffer, 6);
}
static void read_line_returns_length_of_line(void)
{
test_input = "foobar\nbazquux";
test_input_len = strlen(test_input);
char buffer[10];
const int len = read_line(test_get_byte, buffer, sizeof(buffer));
ASSERT_EQUAL(6, len);
}
static void read_line_returns_minus_1_on_eof(void)
{
test_input_len = 0;
char buffer[10];
const int len = read_line(test_get_byte, buffer, sizeof(buffer));
ASSERT_EQUAL(-1, len);
}
static void read_line_returns_buffer_size_if_no_newline(void)
{
test_input = "foobarbazquux";
test_input_len = strlen(test_input);
char buffer[10];
const int len = read_line(test_get_byte, buffer, sizeof(buffer));
ASSERT_EQUAL(10, len);
}
int main(void) int main(void)
{ {
TESTING_BEGIN(); TESTING_BEGIN();
@ -256,5 +304,9 @@ int main(void)
RUN_TEST(spaces_inside_parens_are_ignored); RUN_TEST(spaces_inside_parens_are_ignored);
RUN_TEST(leading_spaces_are_ignored); RUN_TEST(leading_spaces_are_ignored);
RUN_TEST(trailing_spaces_are_ignored); RUN_TEST(trailing_spaces_are_ignored);
RUN_TEST(read_line_copies_line_into_buffer);
RUN_TEST(read_line_returns_length_of_line);
RUN_TEST(read_line_returns_minus_1_on_eof);
RUN_TEST(read_line_returns_buffer_size_if_no_newline);
TESTING_END(); TESTING_END();
} }

View File

@ -1,145 +0,0 @@
#include "repl.h"
#include "testing.h"
#include <stdio.h>
#include <string.h>
static const char *input;
static int input_len;
static bool read_called;
static const char *read_input;
static int read_len;
static const expression_t *read_result;
static bool evaluate_called;
static const expression_t *evaluate_expression;
static int evaluate_result;
static bool print_called;
static const char *print_output;
static int print_len;
static void reset_fixtures(void)
{
read_called = false;
evaluate_called = false;
print_called = false;
}
static int test_get_byte(void)
{
if (0 == input_len)
return EOF;
--input_len;
return *input++;
}
static const expression_t *
test_read(memory_pool_t *pool, const char *input, int len)
{
(void)pool;
read_called = true;
read_input = input;
read_len = len;
return read_result;
}
static int test_evaluate(const expression_t *expression)
{
evaluate_called = true;
evaluate_expression = expression;
return evaluate_result;
}
static void test_print(const char *output, int len)
{
print_called = true;
print_output = output;
print_len = len;
}
static repl_t repl = {
.get_byte = test_get_byte,
.read = test_read,
.evaluate = test_evaluate,
.print = test_print,
};
static void set_up_valid_state(void)
{
static const expression_t expression
= { .is_number = true, .number = 1234 };
input = "foobar\nbarquux";
input_len = 14;
read_result = &expression;
evaluate_result = 4321;
}
static void read_is_called_on_first_line_line_in_input(void)
{
reset_fixtures();
set_up_valid_state();
step_repl(&repl);
ASSERT_TRUE(read_called);
ASSERT_EQUAL(6, read_len);
ASSERT_MEM_EQUAL("foobar", read_input, 6);
}
static void read_result_is_passed_to_evaluate(void)
{
reset_fixtures();
set_up_valid_state();
step_repl(&repl);
ASSERT_TRUE(evaluate_called);
ASSERT_TRUE(evaluate_expression->is_number);
ASSERT_EQUAL(1234, evaluate_expression->number);
}
static void result_of_evaluation_is_printed_with_a_newline(void)
{
reset_fixtures();
set_up_valid_state();
step_repl(&repl);
ASSERT_TRUE(print_called);
ASSERT_EQUAL(5, print_len);
ASSERT_MEM_EQUAL("4321\n", print_output, 5);
}
static void true_is_returned_on_successful_step(void)
{
reset_fixtures();
set_up_valid_state();
const bool result = step_repl(&repl);
ASSERT_TRUE(result);
}
static void false_is_returned_on_end_of_input(void)
{
reset_fixtures();
set_up_valid_state();
input_len = 0;
const bool result = step_repl(&repl);
ASSERT_FALSE(result);
}
static void evaluate_is_not_called_if_read_returns_null(void)
{
reset_fixtures();
set_up_valid_state();
read_result = NULL;
step_repl(&repl);
ASSERT_FALSE(evaluate_called);
}
int main(void)
{
TESTING_BEGIN();
RUN_TEST(read_is_called_on_first_line_line_in_input);
RUN_TEST(read_result_is_passed_to_evaluate);
RUN_TEST(result_of_evaluation_is_printed_with_a_newline);
RUN_TEST(true_is_returned_on_successful_step);
RUN_TEST(false_is_returned_on_end_of_input);
RUN_TEST(evaluate_is_not_called_if_read_returns_null);
TESTING_END();
}