diff --git a/homework_5/CMakeLists.txt b/homework_5/CMakeLists.txt index 77fc499..bd0046f 100644 --- a/homework_5/CMakeLists.txt +++ b/homework_5/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(stack) add_subdirectory(task_1) add_subdirectory(task_2) +add_subdirectory(task_3) diff --git a/homework_5/README.md b/homework_5/README.md index 7c69e58..4a9055f 100644 --- a/homework_5/README.md +++ b/homework_5/README.md @@ -3,3 +3,5 @@ [Task 1. Advanced brackets balance](/homework_5/task_1) [Task 2. Postfix calculator](/homework_5/task_2) + +[Task 3. Shunting yard](/homework_5/task_3) diff --git a/homework_5/task_3/CMakeLists.txt b/homework_5/task_3/CMakeLists.txt new file mode 100644 index 0000000..2d1fc3b --- /dev/null +++ b/homework_5/task_3/CMakeLists.txt @@ -0,0 +1,11 @@ +project("${homeworkName}_task_3") + +add_library(shuntingYard shuntingYard.c) + +add_executable(${PROJECT_NAME} main.c) +target_link_libraries(${PROJECT_NAME} shuntingYard) +target_link_libraries(${PROJECT_NAME} stack) + +add_executable(${PROJECT_NAME}_test test.c) +target_link_libraries(${PROJECT_NAME}_test shuntingYard) +target_link_libraries(${PROJECT_NAME}_test stack) diff --git a/homework_5/task_3/main.c b/homework_5/task_3/main.c new file mode 100644 index 0000000..2aea83e --- /dev/null +++ b/homework_5/task_3/main.c @@ -0,0 +1,72 @@ +#include +#include + +#include "shuntingYard.h" + +bool consumeInput(ShuntingYard *yard, char input) { + char buffer[1024] = { 0 }; + int safeBufferLength = 1024 - 1; + int bufferPosition = 0; + + switch (shuntingYardConsumeInput(yard, input, buffer, safeBufferLength, &bufferPosition)) + { + case YARD_OK: + break; + + case YARD_ALLOCATION_ERROR: + printf("allocation error\n"); + return false; + + case YARD_INCORRECT_INPUT: + printf("incorrect input - probably missed an opening parenthesis\n"); + return false; + + case YARD_BUFFER_OVERFLOW: + printf("buffer ovwerflow - input was too long\n"); + return false; + + case YARD_UNKNOWN_INPUT: + printf("unknown character: '%c'\n", input); + return false; + } + + printf("%s", buffer); + return true; +} + +bool readAndEvaluate(ShuntingYard *yard) { + printf("input infix notation expression (allowed characters: ()0123456789+-*/) (you can use space to split characters):\n"); + printf(" > "); + + while (true) { + char input = getchar(); + if (input == EOF || input == '\n') { + break; + } + + if (input == ' ') { + continue; + } + + if (!consumeInput(yard, input)) { + return false; + } + } + return consumeInput(yard, '\0'); +} + +int main(void) { + ShuntingYard *yard; + + if (!shuntingYardCreate(&yard)) { + printf("allocation failed"); + return 1; + } + + if (!readAndEvaluate(yard)) { + shuntingYardDispose(yard); + return 1; + } + + shuntingYardDispose(yard); +} diff --git a/homework_5/task_3/shuntingYard.c b/homework_5/task_3/shuntingYard.c new file mode 100644 index 0000000..3fc153e --- /dev/null +++ b/homework_5/task_3/shuntingYard.c @@ -0,0 +1,129 @@ +#include "shuntingYard.h" + +#include +#include +#include + +#include "../stack/stack.h" + +typedef struct ShuntingYard { + Stack *stack; +} ShuntingYard; + +bool shuntingYardCreate(ShuntingYard **yard) { + *yard = malloc(sizeof(ShuntingYard)); + if (*yard == NULL) { + return false; + } + + if (!stackCreate(&(*yard)->stack)) { + return false; + } + + return true; +} + +static int getPrecedence(char operator) { + switch (operator) + { + case '+': + case '-': + return 1; + + case '*': + case '/': + return 2; + + default: + return -1; + } +} + +bool writeToBuffer(char output, char *buffer, int bufferLength, int *bufferPosition) { + buffer[*bufferPosition] = output; + ++*bufferPosition; + return *bufferPosition < bufferLength; +} + +YardConsumeResult shuntingYardConsumeInput(ShuntingYard *yard, char input, char *outputBuffer, int bufferLength, int *bufferPosition) { + if (*bufferPosition >= bufferLength) { + return YARD_BUFFER_OVERFLOW; + } + + switch (input) + { + case '(': + if (!stackPush(yard->stack, input)) { + return YARD_ALLOCATION_ERROR; + } + return YARD_OK; + + case '+': + case '-': + case '*': + case '/': + { + uint64_t stackTop = 0; + while (stackTryPeek(yard->stack, &stackTop)) { + char chr = (char)stackTop; + + if (getPrecedence(chr) < getPrecedence(input)) { + break; + } + + stackTryPop(yard->stack, &stackTop); + if (!writeToBuffer(chr, outputBuffer, bufferLength, bufferPosition)) { + return YARD_BUFFER_OVERFLOW; + } + } + + if (!stackPush(yard->stack, input)) { + return YARD_ALLOCATION_ERROR; + } + return YARD_OK; + } + + case ')': + { + uint64_t stackTop = 0; + while (stackTryPop(yard->stack, &stackTop)) { + char chr = (char)stackTop; + + if (chr == '(') { + return YARD_OK; + } + if (!writeToBuffer(chr, outputBuffer, bufferLength, bufferPosition)) { + return YARD_BUFFER_OVERFLOW; + } + } + + return YARD_INCORRECT_INPUT; + } + + case '\0': + { + uint64_t stackTop = 0; + while (stackTryPop(yard->stack, &stackTop)) { + if (!writeToBuffer((char)stackTop, outputBuffer, bufferLength, bufferPosition)) { + return YARD_BUFFER_OVERFLOW; + } + } + + return YARD_OK; + } + + default: + if (input >= '0' && input <= '9') { + if (!writeToBuffer(input, outputBuffer, bufferLength, bufferPosition)) { + return YARD_BUFFER_OVERFLOW; + } + return YARD_OK; + } + return YARD_UNKNOWN_INPUT; + } +} + +void shuntingYardDispose(ShuntingYard *yard) { + stackDispose(yard->stack); + free(yard); +} diff --git a/homework_5/task_3/shuntingYard.h b/homework_5/task_3/shuntingYard.h new file mode 100644 index 0000000..ed7155b --- /dev/null +++ b/homework_5/task_3/shuntingYard.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +/// @brief Structure for converting infix notation to postfix (also known as Reverse Polish notation (PRN)) +typedef struct ShuntingYard ShuntingYard; + +typedef enum { + YARD_OK, + YARD_UNKNOWN_INPUT, + YARD_INCORRECT_INPUT, + YARD_BUFFER_OVERFLOW, + YARD_ALLOCATION_ERROR +} YardConsumeResult; + +/// @brief Creates `ShuntingYard` +/// @param yard Pointer to store created `ShuntingYard` to +/// @return `true` if successful, `false` otherwise (allocation failed) +bool shuntingYardCreate(ShuntingYard **yard); + +/// @brief Tries to read input to shunting yard +/// @param yard Pointer to `ShuntingYard` instance +/// @param input Input to read (use `'\0'` flush remaining characters to buffer) +/// @param outputBuffer Buffer to write to +/// @param bufferLength Length of buffer; used to prevent buffer overflow +/// @param bufferPosition Position in buffer to write character into +/// @return `YARD_OK` if read successfully +/// +/// `YARD_UNKNOWN_INPUT` if unknown character was provided +/// +/// `YARD_INCORRECT_INPUT` if previously characters were provided in incorrect format, +/// probably an opening parenthesis was missed +/// +/// `YARD_BUFFER_OVERFLOW` if `bufferPosition` exceeded `bufferLength` +/// +/// `YARD_ALLOCATION_ERROR` if allocation error ocurred internally +YardConsumeResult shuntingYardConsumeInput(ShuntingYard *yard, char input, char *outputBuffer, int bufferLength, int *bufferPosition); + +/// @brief Disposes shunting yard +/// @param yard `ShuntingYard` to dispose +void shuntingYardDispose(ShuntingYard *yard); diff --git a/homework_5/task_3/test.c b/homework_5/task_3/test.c new file mode 100644 index 0000000..15ac2b2 --- /dev/null +++ b/homework_5/task_3/test.c @@ -0,0 +1,100 @@ +#define CTEST_MAIN +#define CTEST_SEGFAULT +#include "../../ctest/ctest.h" + +#include + +#include "../stack/stack.h" +#include "shuntingYard.h" + +int main(int argc, const char *argv[]) { + return ctest_main(argc, argv); +} + +ShuntingYard *createYard(void) { + ShuntingYard *yard; + ASSERT_TRUE(shuntingYardCreate(&yard)); + ASSERT_NOT_NULL(yard); + + return yard; +} + +void assertOutput(ShuntingYard *yard, const char *input, const char *expectedOutput) { +#define BUFFER_LENGTH 1024 + char buffer[BUFFER_LENGTH] = { 0 }; + int bufferPosition = 0; + + for (int i = 0; input[i] != '\0'; ++i) { + ASSERT_EQUAL(shuntingYardConsumeInput(yard, input[i], buffer, BUFFER_LENGTH, &bufferPosition), YARD_OK); + } + + ASSERT_EQUAL(shuntingYardConsumeInput(yard, '\0', buffer, BUFFER_LENGTH, &bufferPosition), YARD_OK); + + ASSERT_STR(buffer, expectedOutput); + +#undef BUFFER_LENGTH +} + +void testYard(const char *input, const char *expectedOutput) { + ShuntingYard *yard = createYard(); + assertOutput(yard, input, expectedOutput); + shuntingYardDispose(yard); +} + +CTEST(yardTests, createTest) { + ShuntingYard *yard = createYard(); + shuntingYardDispose(yard); +} + +CTEST(yardTests, singleNumberTest) { + testYard("0", "0"); + testYard("5", "5"); +} + +CTEST(yardTests, simpleOperationTest) { + testYard("0+0", "00+"); + testYard("5+5", "55+"); + + testYard("5-7", "57-"); + testYard("5-5", "55-"); + + testYard("0*5", "05*"); + testYard("8*4", "84*"); + + testYard("4/2", "42/"); + testYard("9/4", "94/"); +} + +CTEST(yardTests, simpleOperationInParenthesesTest) { + testYard("(0+0)", "00+"); + testYard("(5+5)", "55+"); + + testYard("(5-7)", "57-"); + testYard("(5-5)", "55-"); + + testYard("(0*5)", "05*"); + testYard("(8*4)", "84*"); + + testYard("(4/2)", "42/"); + testYard("(9/4)", "94/"); +} + +CTEST(yardTests, operationBetweenParenthesesAndNumberTest) { + testYard("(0+0)*4", "00+4*"); + testYard("(5+5)-3", "55+3-"); + + testYard("(5-7)/1", "57-1/"); + testYard("1+(5-5)", "155-+"); + + testYard("(0*5)*9", "05*9*"); + testYard("2+(8*4)", "284*+"); + + testYard("(4/2)-8", "42/8-"); + testYard("(9/4)*6", "94/6*"); +} + +CTEST(yardTests, operationBetweenTwoParenthesesTest) { + testYard("(4-1)*(8+1)", "41-81+*"); + testYard("(9*2)/(9-2)", "92*92-/"); + testYard("(6/3)+(8/2)", "63/82/+"); +}