diff --git a/homework_5/CMakeLists.txt b/homework_5/CMakeLists.txt index 170e71d..77fc499 100644 --- a/homework_5/CMakeLists.txt +++ b/homework_5/CMakeLists.txt @@ -5,3 +5,4 @@ set(homeworkName "${PROJECT_NAME}") add_subdirectory(stack) add_subdirectory(task_1) +add_subdirectory(task_2) diff --git a/homework_5/README.md b/homework_5/README.md index 72e66a9..7c69e58 100644 --- a/homework_5/README.md +++ b/homework_5/README.md @@ -1,3 +1,5 @@ # Homework 5 [Task 1. Advanced brackets balance](/homework_5/task_1) + +[Task 2. Postfix calculator](/homework_5/task_2) diff --git a/homework_5/task_2/CMakeLists.txt b/homework_5/task_2/CMakeLists.txt new file mode 100644 index 0000000..a625d7e --- /dev/null +++ b/homework_5/task_2/CMakeLists.txt @@ -0,0 +1,11 @@ +project("${homeworkName}_task_2") + +add_library(postfixCalc postfixCalc.c) + +add_executable(${PROJECT_NAME} main.c) +target_link_libraries(${PROJECT_NAME} postfixCalc) +target_link_libraries(${PROJECT_NAME} stack) + +add_executable(${PROJECT_NAME}_test test.c) +target_link_libraries(${PROJECT_NAME}_test postfixCalc) +target_link_libraries(${PROJECT_NAME}_test stack) \ No newline at end of file diff --git a/homework_5/task_2/main.c b/homework_5/task_2/main.c new file mode 100644 index 0000000..ffa3722 --- /dev/null +++ b/homework_5/task_2/main.c @@ -0,0 +1,72 @@ +#include +#include + +#include "postfixCalc.h" + +bool readAndEvaluate(PostfixCalc *calc) { + printf("input digits and operands (allowed characters: 0123456789+-*/) (you can use space to split them):\n"); + printf(" > "); + + while (true) { + char input = getchar(); + if (input == EOF || input == '\n') { + break; + } + + if (input == ' ') { + continue; + } + + switch (calcConsumeInput(calc, input)) + { + case CALC_CONSUME_OK: + break; + case CALC_CONSUME_ALLOCATION_ERROR: + printf("allocation error\n"); + return false; + + case CALC_CONSUME_NOT_ENOUGH_OPERANDS: + printf("not enough operands to perform operation '%c'\n", input); + return false; + + case CALC_CONSUME_UNKNOWN_INPUT: + printf("unknown character: '%c'\n", input); + return false; + } + } + + int result = 0; + + switch (calcTryGetResult(calc, &result)) + { + case CALC_GET_RESULT_OK: + printf("evaluation result: %d\n", result); + break; + + case CALC_IS_EMPTY_BEFORE_READ: + printf("nothing to evaluate\n"); + return false; + + case CALC_NOT_EMPTY_AFTER_READ: + printf("not all numbers are used\n"); + return false; + } + + return true; +} + +int main(void) { + PostfixCalc *calc; + + if (!calcCreate(&calc)) { + printf("allocation failed"); + return 1; + } + + if (!readAndEvaluate(calc)) { + calcDispose(calc); + return 1; + } + + calcDispose(calc); +} diff --git a/homework_5/task_2/postfixCalc.c b/homework_5/task_2/postfixCalc.c new file mode 100644 index 0000000..8f5fd84 --- /dev/null +++ b/homework_5/task_2/postfixCalc.c @@ -0,0 +1,91 @@ +#include "postfixCalc.h" + +#include +#include + +#include "../stack/stack.h" + +typedef struct PostfixCalc { + Stack *stack; +} PostfixCalc; + +bool calcCreate(PostfixCalc **calc) { + *calc = malloc(sizeof(PostfixCalc)); + if (*calc == NULL) { + return false; + } + + if (!stackCreate(&(*calc)->stack)) { + return false; + } + + return true; +} + +CalcConsumeResult calcConsumeInput(PostfixCalc *calc, char input) { + switch (input) + { + case '+': + case '-': + case '*': + case '/': + uint64_t valueA = -1; + if (!stackTryPop(calc->stack, &valueA)) { + return CALC_CONSUME_NOT_ENOUGH_OPERANDS; + } + + uint64_t valueB = -1; + if (!stackTryPop(calc->stack, &valueB)) { + return CALC_CONSUME_NOT_ENOUGH_OPERANDS; + } + + int left = (int)valueB; + int right = (int)valueA; + + int value = 0; + if (input == '+') { + value = left + right; + } else if (input == '-') { + value = left - right; + } else if (input == '*') { + value = left * right; + } else if (input == '/') { + value = left / right; + } + + if (!stackPush(calc->stack, (uint64_t)value)) { + return CALC_CONSUME_ALLOCATION_ERROR; + } + + break; + + default: + if (input >= '0' && input <= '9') { + uint64_t value = input - '0'; + if (!stackPush(calc->stack, value)) { + return CALC_CONSUME_ALLOCATION_ERROR; + } + } else { + return CALC_CONSUME_UNKNOWN_INPUT; + } + + break; + } + + return CALC_CONSUME_OK; +} + +CalcGetResultResult calcTryGetResult(PostfixCalc *calc, int *result) { + uint64_t value = 0; + if (!stackTryPop(calc->stack, &value)) { + return CALC_IS_EMPTY_BEFORE_READ; + } + + *result = (int)value; + return stackIsEmpty(calc->stack) ? CALC_GET_RESULT_OK : CALC_NOT_EMPTY_AFTER_READ; +} + +void calcDispose(PostfixCalc *calc) { + stackDispose(calc->stack); + free(calc); +} diff --git a/homework_5/task_2/postfixCalc.h b/homework_5/task_2/postfixCalc.h new file mode 100644 index 0000000..f0ffe86 --- /dev/null +++ b/homework_5/task_2/postfixCalc.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +/// @brief Calculator that works with basic operations like +/// addition, subtraction, multiplication and division +/// using integer numbers. +/// +/// Recieves expression character by character in +/// postfix notation(also known as Reverse Polish notation(PRN)) +typedef struct PostfixCalc PostfixCalc; + +typedef enum { + CALC_CONSUME_OK, + CALC_CONSUME_NOT_ENOUGH_OPERANDS, + CALC_CONSUME_ALLOCATION_ERROR, + CALC_CONSUME_UNKNOWN_INPUT +} CalcConsumeResult; + +typedef enum { + CALC_GET_RESULT_OK, + CALC_IS_EMPTY_BEFORE_READ, + CALC_NOT_EMPTY_AFTER_READ +} CalcGetResultResult; + +/// @brief Creates `PostfixCalc` +/// @param calc Pointer to store created `PostfixCalc` to +/// @return `true` if successful, `false` otherwise (allocation failed) +bool calcCreate(PostfixCalc **calc); + +/// @brief Tries to read input to calculator +/// @param calc Pointer to `PostfixCalc` instance +/// @param input Input to read +/// @return `CALC_CONSUME_OK` if read successfully +/// +/// `CALC_CONSUME_NOT_ENOUGH_OPERANDS` if there were not enough operands provided before specified operator +/// +/// `CALC_CONSUME_UNKNOWN_INPUT` if unknown character was provided +/// +/// `CALC_CONSUME_ALLOCATION_ERROR` if allocation error ocurred internally +CalcConsumeResult calcConsumeInput(PostfixCalc *calc, char input); + +/// @brief Tries to get last result +/// @param calc Pointer to `PostfixCalc` instance +/// @param result Pointer to write evaluation result to +/// @return `CALC_GET_RESULT_OK` if evaluated successfully +/// +/// `CALC_IS_EMPTY_BEFORE_READ` if there wasn't any result to return +/// +/// `CALC_NOT_EMPTY_AFTER_READ` if there are more than one result +CalcGetResultResult calcTryGetResult(PostfixCalc *calc, int *result); + +/// @brief Disposes calculator +/// @param calc `PostfixCalc` to dispose +void calcDispose(PostfixCalc *calc); diff --git a/homework_5/task_2/test.c b/homework_5/task_2/test.c new file mode 100644 index 0000000..753b85e --- /dev/null +++ b/homework_5/task_2/test.c @@ -0,0 +1,102 @@ +#define CTEST_MAIN +#define CTEST_SEGFAULT +#include "../../ctest/ctest.h" + +#include "postfixCalc.h" + +int main(int argc, const char *argv[]) { + return ctest_main(argc, argv); +} + +PostfixCalc *createCalc(void) { + PostfixCalc *calc; + ASSERT_TRUE(calcCreate(&calc)); + ASSERT_NOT_NULL(calc); + + return calc; +} + +void assertResult(PostfixCalc *calc, int expected) { + int result = 0; + ASSERT_EQUAL(calcTryGetResult(calc, &result), CALC_GET_RESULT_OK); + ASSERT_EQUAL(expected, result); +} + +void assertConsumeString(PostfixCalc *calc, const char *input, int expected) { + for (int i = 0; input[i] != '\0'; ++i) { + ASSERT_EQUAL(calcConsumeInput(calc, input[i]), CALC_CONSUME_OK); + } + + assertResult(calc, expected); +} + +void testCalc(const char *input, int expected) { + PostfixCalc *calc = createCalc(); + + assertConsumeString(calc, input, expected); + + calcDispose(calc); +} + +CTEST(postfixCalcTests, createTest) { + PostfixCalc *calc = createCalc(); + calcDispose(calc); +} + +CTEST(postfixCalcTests, singleNumberTest) { + testCalc("4", 4); +} + +CTEST(postfixCalcTests, additionTest) { + testCalc("12+", 3); + testCalc("00+", 0); + testCalc("50+", 5); + testCalc("09+", 9); + testCalc("78+", 15); + testCalc("99+", 18); +} + +CTEST(postfixCalcTests, subtractionTest) { + testCalc("53-", 2); + testCalc("00-", 0); + testCalc("50-", 5); + testCalc("09-", -9); + testCalc("78-", -1); + testCalc("99-", 0); + testCalc("98-", 1); + testCalc("38-", -5); +} + +CTEST(postfixCalcTests, multiplicationTest) { + testCalc("53*", 15); + testCalc("00*", 0); + testCalc("50*", 0); + testCalc("09*", 0); + testCalc("78*", 56); + testCalc("99*", 81); + testCalc("98*", 72); + testCalc("38*", 24); + testCalc("88*", 64); +} + +CTEST(postfixCalcTests, divisionTest) { + testCalc("51/", 5); + testCalc("09/", 0); + testCalc("78/", 0); + testCalc("99/", 1); + testCalc("93/", 3); + testCalc("31/", 3); + testCalc("88/", 1); + testCalc("82/", 4); + testCalc("62/", 3); + testCalc("63/", 2); +} + +CTEST(postfixCalcTests, complexTest) { + testCalc("12+34++", 10); + testCalc("12+34+*", 21); + testCalc("12+34+*2*", 42); + testCalc("12*34*56***", 720); + + testCalc("96-12+*", 9); +}