```c while (1) { fgets(expr, 200, stdin); ... } ``` What happens if `fgets()` fail? You are ignoring its return value. ---------- ```c if (strncmp(expr, "quit", 4) == 0) { break; } ``` There's no way I'd have known to enter `quit` to exit had I not read the source code. ---------- ```c memset(expr, 0, strlen(expr)); ``` What's the point of setting `expr` to all zero bytes when it is just going to be overwritten and null-terminated by `fgets()`? ---------- ``` Node *parse_to_postfix(char *expr); ``` We wouldn't require this forward declaration if we define `parse_to_postfix()` before `main()`. ---------- ```c char *num_buf = calloc(strlen(expr) + 1, sizeof(char)); ``` `sizeof(char)` is defined by the standard to be 1. You can safely elide it. ---------- ```c if (num_buf == NULL) { perror("error:"); abort(); } ``` There's no need to cause *an abnormal termination* here. Simply do `exit(EXIT_FAILURE)`. ---------- ``` if (isspace(c)) { continue; } ``` `isspace()` expects an `unsigned char`, not a `char`. ---------- ``` Stack *output = stack_create(NULL); Stack *op_stack = stack_create(NULL); ``` The return values go unchecked. Why return a null pointer on `malloc()` failure if you were simply going to ignore it? ---------- You are computing the length of `num_buf` thrice in `parse_to_postfix()`. Consider using an extra variable to save the length. ---------- ```c // debugging purposes // Node *head = bottom; // while (head != NULL) { // node_print(head, 0); // head = head->previous; // } // printf("\n"); ``` This is no longer required. ---------- ```c printf("error: syntax error\n"); ``` Error messages go to `stderr`, not `stdout`. ---------- ```c Node *a = stack_pop(stack), *b = stack_pop(stack); long double result; switch (expr->operator) { case '+': result = b->number + a->number; break; ``` The above would invoke undefined behavior if `stack_pop()` returns `NULL`. ---------- ```c case '/': result = b->number / a->number; ``` This would invoke undefined behavior if `a->number` was 0. ---------- ``` Node *parse_to_postfix(char *expr); ``` `parse_to_postfix()` expects a non-null pointer, and it must be declared as such: ``` Node *parse_to_prefix(char expr[static 1]); ``` Same goes for the rest of the functions. This requires C99 albeit. ---------- ```c void node_print(Node *target, int newline) { if (target == NULL) { printf("cannot print NULL node\n"); return; } char nl = (newline) ? '\n' : ' '; ``` `newline` only needs to denote two states here, so it should be of type `bool`. (Include `stdbool.h` prior for the definition of `bool` prior to C2X.) The parentheses around `newline` are also unneeded. ---------- ```c Node *new = malloc(sizeof(*new)); ``` `new` is a bad identifier name since it is a reserved keyword in C++. Even if you never care for porting/compatibility with C++, you should still avoid naming identifiers after keywords from that language, since IDE style formatting tends to have a "C/C++" setting and then it will format `new` in weird ways. Note that you can drop the parentheses around `*new`. ---------- ```c if (target == NULL) { printf("error: cannot remove NULL node\n"); return NULL; } ``` Instead of printing an error message to `stdout` - which should really have been `stderr` - just return `NULL`. Nobody wants a library to print random stuff to `stdout`/`stderr` in their code, especially without their knowledge. ---------- ```c int is_operator(char target) { switch (target) { case '+': break; case '-': break; case '/': break; case '*': break; case '^': break; default: return 0; } return 1; } ``` This over-engineered function can be simplified to: ```c return strchr("+-/*^", target) != NULL; ``` Or you could use `memchr()`. Or: ```c return target == '+' || target == '-' || target == '*' || target == '/' || target == '^'; ``` ---------- ```c int get_precedence(char operator) { switch (operator) { case '+': return 1; case '-': return 1; case '/': return 2; case '*': return 2; case '^': return 3; default: printf("error: cannot get precendence of unknown operator '%c'\n", operator); return 0; } } ``` You could use a map for this: ``` int get_precedence(char operator) { struct { const char op; const unsigned int precedence; } ops = { {'+', 1}, {'-', 1}, {'/', 2}, {'*', 2}, {...} }; ``` But that might be overkill. Or you could merge cases: ```c case '+': case '-': return 1; case '/': case '*': return 2; case '*': return 3; default: return 0; ``` ---------- ```c int is_left_assoc(char operator) { switch (operator) { case '+': return 1; case '-': return 1; case '/': return 1; case '*': return 1; case '^': return 0; default: printf("error: cannot get associativity of unknown operator '%c'\n", operator); return 0; } } ``` Same goes for this: ```c case '+': case '-': case '*': case '/': return 1; default: return 0; ``` ---------- ```c int node_check_valuetype(Node *target, enum NodeType type) { return (target == NULL || target->value_type != type) ? 0 : 1; } ``` This can be simplified to: ```c return target && target->value_type == type; ``` ### Some comments on the stack implementation: I believe you have overly complicated it. Like @Davilsor suggests, a dynamic array with its top element at the end would have been more performant, more maintainable, and easier to implement. It would also have respected [principle of locality](https://en.wikipedia.org/wiki/Locality_of_reference). I also do not like that `stack`'s internals are known to the client, i.e. its definition is included in `util.h`. I would have expected it to be an opaque type, with `get_top()`, `get_size()` et cetera. The functions that are internal to the implementation should be defined as having internal linkage, i.e. marked `static` in the translation unit, and their declarations should be removed from the corresponding header file. Consider using a common prefix for all the symbols. Currently, you have `stack_`, `node_`, et cetera.