From d665218448a1a65238f3c1017004d0b849e3cbd8 Mon Sep 17 00:00:00 2001 From: sloven-c Date: Wed, 29 Oct 2025 19:46:56 +0100 Subject: [PATCH] Initial commit --- CMakeLists.txt | 11 ++ main.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++++ stack.c | 162 +++++++++++++++++++++++++++ stack.h | 52 +++++++++ structures.h | 24 ++++ 5 files changed, 544 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 main.c create mode 100644 stack.c create mode 100644 stack.h create mode 100644 structures.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3541861 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 4.0) +project(calculator C) + +set(CMAKE_C_STANDARD 23) + +add_executable(calculator main.c + structures.h + stack.c + stack.h +) +target_link_libraries(calculator m) \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..a6a3f47 --- /dev/null +++ b/main.c @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include + +#include "stack.h" +#include "structures.h" + +int calculate_stack(const stack *output_queue); + +int str_to_num(char *string); + +int calculate(int a, int b, char op); + +string get_expression(int argc, char *argv[]); + +stack get_output_queue(const stack *tokenisedExpression); + +int push_operator(char operatorToPush, stack *output_stack, stack *operator_stack); + +bool stack_higher_precedence(char op, char stackOp); + +stack split_into_tokens(string expression); + +string get_slice(int a, size_t b, const char *expression); + +bool validate_expression(const char *expression); + +charResult validate_char(char c); + +void validate_malloc(const void *p); + +void throw_error(char *msg); + +const char *OPERATORS[] = {"+-", "*/", "^", "()"}; + +int main(const int argc, char *argv[]) { + const string expression = get_expression(argc, argv); + if (expression.string == nullptr) return 0; + + stack tokens = split_into_tokens(expression); + stack_print(&tokens, "Tokens"); + + stack output_queue = get_output_queue(&tokens); + + printf("%d\n", calculate_stack(&output_queue)); + + stack_deinit(&output_queue); + stack_deinit(&tokens); + free(expression.string); + + return 0; +} + +int calculate_stack(const stack *output_queue) { + stack calculation_stack = stack_init(IntArray, output_queue->len); + + for (size_t i = 0; i <= output_queue->i; i++) { + const stackData element = stack_get(output_queue, i); + if (element.ret_code == 1) + throw_error("Couldn't retrieve element from output queue for calculation stack push"); + + switch (validate_char(element.data.string[0]).type) { + case Digit: + const int n = str_to_num(element.data.string); + const int r = stack_push(&calculation_stack, (stackInput){.n = n}, false); + if (r == 1) throw_error("Failed to push number to calculation stack"); + break; + case Operator: + const stackData aP = stack_pop(&calculation_stack); + const stackData bP = stack_pop(&calculation_stack); + if (aP.ret_code == 1 || bP.ret_code == 1) throw_error("Failed to pop numbers from calculation stack"); + + const int result = calculate(aP.data.n, bP.data.n, element.data.string[0]); + const int rSp = stack_push(&calculation_stack, (stackInput){.n = result}, false); + if (rSp == 1) throw_error("Error while trying to push result into calculation stack"); + + break; + case Invalid: + fprintf(stderr, "The following string is not valid part of expression: '%s'\n", element.data.string); + throw_error(nullptr); + } + } + + const stackData res = stack_get(&calculation_stack, 0); + if (res.ret_code == 1) { + fprintf(stderr, "Calculation stack index should be at position 0 but is at %d!\n", calculation_stack.i); + throw_error(nullptr); + } + + return res.data.n; +} + +int str_to_num(char *string) { + char *endptr; + const int num = (int) strtol(string, &endptr, 10); + + if (endptr == string || *endptr != 0x0) { + fprintf(stderr, "Failed to convert the '%s' string to number\n", string); + throw_error(nullptr); + } + + return num; +} + +// ReSharper disable once CppNotAllPathsReturnValue +int calculate(const int a, const int b, const char op) { + switch (op) { + case '+': + return a + b; + case '-': + return a - b; + case '*': + return a * b; + case '/': + return a / b; + case '^': + return (int) pow(a, b); + } + + fprintf(stderr, "Operator '%c' is invalid\n", op); + throw_error(nullptr); +} + +string get_expression(const int argc, char *argv[]) { + string ex = {.string = nullptr}; + if (argc <= 1) return ex; // first argument is just program name + + size_t len = 0; + for (size_t i = 1; i < argc; i++) { + len += strlen(argv[i]); + } + const size_t totalLen = len * sizeof(char) + 1; // +1 => \0 (0x0) + + char *expression = malloc(totalLen); + validate_malloc(expression); + + expression[0] = '\0'; // since the following will cat string, we need to set the end or rather start point + + for (size_t i = 1; i < argc; i++) { + strlcat(expression, argv[i], totalLen); + } + + if (validate_expression(expression)) { + ex.string = expression; + ex.len = totalLen; + } else { + free(expression); + } + + return ex; +} + +// we could not return anything and just send pointer to stack? +stack get_output_queue(const stack *tokenisedExpression) { + stack output_stack = stack_init(StringArray, tokenisedExpression->len); + stack operator_stack = stack_init(CharArray, tokenisedExpression->len); + + for (size_t i = 0; i < tokenisedExpression->i; i++) { + const stackData token = stack_get(tokenisedExpression, i); + if (token.ret_code == 1) throw_error("Failed to retrieve token"); + + if (validate_char(token.data.string[0]).type == Operator) { + // do operator shit + const int ret = push_operator(token.data.string[0], &output_stack, &operator_stack); + if (ret == 1) throw_error("Failed to push operator"); + } else { + // push number to output queue + stack_push(&output_stack, token.data, false); + } + } + + // pop everything from operator to output stack + while (operator_stack.i >= 0) { + const stackData popOp = stack_pop(&operator_stack); + if (popOp.ret_code == 1) throw_error("Failed to retrieve operator from stack"); + + const int ret = stack_push(&output_stack, popOp.data, true); + if (ret == 1) throw_error("Failed to push operator to output"); + } + + stack_deinit(&operator_stack); + return output_stack; +} + +// push operator to operator queue and +int push_operator(const char operatorToPush, stack *output_stack, stack *operator_stack) { + if (operatorToPush == '(') { + return stack_push(operator_stack, (stackInput){.ch = operatorToPush}, false); + } + if (operatorToPush == ')') { + for (stackData el = stack_pop(operator_stack); el.data.ch != '('; el = stack_pop(operator_stack)) { + if (el.ret_code == 1) return 1; + + const int r = stack_push(output_stack, el.data, true); + if (r == 1) return 1; + } + + return 0; + } + + for (stackData lastStackOp = stack_get(operator_stack, operator_stack->i); + stack_higher_precedence(operatorToPush, lastStackOp.data.ch); + lastStackOp = stack_get(operator_stack, operator_stack->i)) { + if (lastStackOp.ret_code == 1) return 1; + + const stackData opToPush = stack_pop(operator_stack); + if (opToPush.ret_code == 1) return 1; + + const int r = stack_push(output_stack, opToPush.data, true); + if (r == 1) return 1; + } + + return stack_push(operator_stack, (stackInput){.ch = operatorToPush}, true); +} + +bool stack_higher_precedence(const char op, const char stackOp) { + return validate_char(stackOp).opPrecedence > validate_char(op).opPrecedence; +} + +stack split_into_tokens(const string expression) { + stack tokenisedExpression = stack_init(StringArray, expression.len); + int lastOp = -1; + + for (size_t i = 0; i < expression.len; i++) { + if (validate_char(expression.string[i]).type != Operator) continue; + + const string slice = get_slice(lastOp, i, expression.string); + if (slice.string != nullptr) { + const int ret = stack_push(&tokenisedExpression, (stackInput){.string = slice.string}, false); + if (ret == 1) throw_error("Failed to push slice into stack"); + } + + const int ret = stack_push(&tokenisedExpression, (stackInput){.ch = expression.string[i]}, true); + if (ret == 1) throw_error("Failed to push current operator into stack"); + + lastOp = i; + } + + return tokenisedExpression; +} + +// todo fix edge case on beginning +string get_slice(int a, size_t b, const char *expression) { + const size_t len = b - abs(a) - 1; + if (len < 1) return (string){.string = nullptr}; + + const size_t total_len = len * sizeof(char) + 1; + + char *slice = malloc(total_len); + validate_malloc(slice); + + + for (const size_t i = ++a; a < b; a++) { + // we don't track i separately but how much has a moved since we started creating the slice + slice[a - i] = expression[a]; + } + + return (string){slice, total_len}; +} + +bool validate_expression(const char *expression) { + for (const char *it = expression; *it != '\0'; it++) { + if (validate_char(*it).type == Invalid) return false; + } + + return true; +} + +charResult validate_char(const char c) { + if (isdigit(c)) return (charResult){Digit, 0}; + + for (int i = 0; i < sizeof(OPERATORS); i++) { + for (int j = 0; j < sizeof(OPERATORS[i]); j++) { + if (c == OPERATORS[i][j]) + return (charResult){Operator, i}; + } + } + + return (charResult){Invalid, 0}; +} + +void validate_malloc(const void *p) { + if (p != NULL) return; + + throw_error("Failed to allocate memory"); +} + +void throw_error(char *msg) { + if (msg != nullptr) + fprintf(stderr, "%s\n", msg); + + exit(1); +} diff --git a/stack.c b/stack.c new file mode 100644 index 0000000..57f304b --- /dev/null +++ b/stack.c @@ -0,0 +1,162 @@ +// +// Created by marto on 27. 10. 25. +// + +#include +#include +#include "stack.h" + +#include + +stack stack_init(const DataType type, const int len) { + stack stack = { + .type = type, + .i = -1, + .len = len, + }; + + const size_t arrLen = len * sizeof(char); + + switch (type) { + case StringArray: + stack.data.sarr = malloc(arrLen); + break; + case CharArray: + stack.data.carr = malloc(arrLen); + break; + case IntArray: + stack.data.narr = malloc(arrLen); + default: + fprintf(stderr, "Failed to recognise the DataType for Stack structure"); + exit(1); + } + + // ReSharper disable once CppSomeObjectMembersMightNotBeInitialized + return stack; +} + +void stack_deinit(stack *stack) { + switch (stack->type) { + case StringArray: + clear_str_array(stack); + free(stack->data.sarr); + stack->data.sarr = nullptr; + break; + case CharArray: + free(stack->data.carr); + stack->data.carr = nullptr; + break; + case IntArray: + free(stack->data.narr); + stack->data.narr = nullptr; + break; + } +} + +int stack_push(stack *stack, const stackInput input, const bool pushCharToString) { + if (stack->i >= stack->len - 1) return 1; + stack->i++; + + switch (stack->type) { + case StringArray: + char *str; + + // if we still push to array of strings but we're pushing single character as string + if (pushCharToString) { + str = malloc(sizeof(char) + 1); + str[0] = input.ch; + str[1] = 0x0; + } else { + str = malloc(strlen(input.string) * sizeof(char) + 1); + strcpy(str, input.string); + } + + stack->data.sarr[stack->i] = str; + break; + case CharArray: + stack->data.carr[stack->i] = input.ch; + break; + case IntArray: + stack->data.narr[stack->i] = input.n; + } + + return 0; +} + +stackData stack_pop(stack *stack) { + if (stack->i == -1) return (stackData){.ret_code = 1}; + stackInput returnData; + + switch (stack->type) { + case StringArray: + returnData.string = stack->data.sarr[stack->i]; + // todo might have to do malloc? + // todo ticking timebomb + break; + case CharArray: + returnData.ch = stack->data.carr[stack->i]; + break; + case IntArray: + returnData.n = stack->data.narr[stack->i]; + break; + } + + stack->i--; + return (stackData){returnData, 0}; +} + +stackData stack_get(const stack *stack, const int n) { + if (stack->i == -1 || n >= stack->len) return (stackData){.ret_code = 1}; + stackInput returnData; + + switch (stack->type) { + case StringArray: + returnData.string = stack->data.sarr[stack->i]; + break; + case CharArray: + returnData.ch = stack->data.carr[stack->i]; + break; + case IntArray: + returnData.n = stack->data.narr[stack->i]; + break; + } + + return (stackData){returnData, 0}; +} + +void stack_print(const stack *stack, const char *name) { + printf("Printing stack %s:\n", name); + if (stack->i == -1) return (void) printf("NULL\n"); + + for (int i = 0; i <= stack->i; i++) { + const stackData el = stack_get(stack, i); + if (el.ret_code == 1) { + fprintf(stderr, "Invalid index\n"); + exit(1); + } + + printf("[%d]: ", i); + + switch (stack->type) { + case StringArray: + printf("'%s'", el.data.string); + break; + case CharArray: + printf("'%c'", el.data.ch); + break; + case IntArray: + printf("'%d'", el.data.n); + break; + } + + printf("\n"); + } + printf("\n"); +} + +static void clear_str_array(const stack *stack) { + for (size_t i = 0; i < stack->len; i++) { + free(stack->data.sarr[i]); + stack->data.sarr[i] = nullptr; + } +} diff --git a/stack.h b/stack.h new file mode 100644 index 0000000..9803969 --- /dev/null +++ b/stack.h @@ -0,0 +1,52 @@ +// +// Created by marto on 27. 10. 25. +// + +#ifndef CALCULATOR_STACK_H +#define CALCULATOR_STACK_H + +typedef enum { + StringArray, + CharArray, + IntArray, +} DataType; + +typedef struct { + DataType type; + + union { + char **sarr; + char *carr; + int *narr; + } data; + + // if i >= len, the program's guardrails have failed + int i, len; +} stack; + +typedef union { + char ch; + char *string; + int n; +} stackInput; + +typedef struct { + stackInput data; + int ret_code; +} stackData; + +stack stack_init(DataType type, int len); + +void stack_deinit(stack *stack); + +int stack_push(stack *stack, stackInput input, bool pushCharToString); + +stackData stack_pop(stack *stack); + +stackData stack_get(const stack *stack, int n); + +void stack_print(const stack *stack, const char *name); + +static void clear_str_array(const stack *stack); + +#endif //CALCULATOR_STACK_H diff --git a/structures.h b/structures.h new file mode 100644 index 0000000..07454a4 --- /dev/null +++ b/structures.h @@ -0,0 +1,24 @@ +// +// Created by marto on 25. 10. 25. +// + +#ifndef CALCULATOR_STRUCTURES_H +#define CALCULATOR_STRUCTURES_H + +typedef enum { + Digit, + Operator, + Invalid, +} charType; + +typedef struct { + charType type; + size_t opPrecedence; +} charResult; + +typedef struct { + char *string; + int len; +} string; + +#endif //CALCULATOR_STRUCTURES_H