summaryrefslogtreecommitdiff
path: root/lab04/expression.h
diff options
context:
space:
mode:
Diffstat (limited to 'lab04/expression.h')
-rw-r--r--lab04/expression.h319
1 files changed, 319 insertions, 0 deletions
diff --git a/lab04/expression.h b/lab04/expression.h
new file mode 100644
index 0000000..f0e2847
--- /dev/null
+++ b/lab04/expression.h
@@ -0,0 +1,319 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef EXPRESSION_H
+#define EXPRESSION_H
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "stack.h"
+
+#define EXPRESSION_NAME_MAX 64
+
+enum expression_error_code {
+ EXPRESSION_OK,
+ EXPRESSION_SYNTAX_ERROR,
+ EXPRESSION_PAREN_ERROR,
+ EXPRESSION_UNKNOWN_VARIABLE,
+ EXPRESSION_UNSET_VARIABLE,
+ EXPRESSION_DIVISION_BY_ZERO,
+};
+
+struct expression_error {
+ enum expression_error_code code;
+ size_t position;
+};
+
+struct expression_variable {
+ char name[EXPRESSION_NAME_MAX];
+ double value;
+ bool assigned;
+};
+
+struct expression_context {
+ struct vec variables;
+};
+
+static inline void expression_init(struct expression_context *ctx)
+{
+ vec_init(&ctx->variables, struct expression_variable);
+}
+
+static inline void expression_destroy(struct expression_context *ctx)
+{
+ vec_destroy(&ctx->variables);
+}
+
+static inline void expression_error(struct expression_error *error,
+ enum expression_error_code code,
+ size_t position)
+{
+ if (error) {
+ error->code = code;
+ error->position = position;
+ }
+}
+
+static inline bool expression_name_start(unsigned char c)
+{
+ return isalpha(c) || c == '_';
+}
+
+static inline bool expression_name_char(unsigned char c)
+{
+ return isalnum(c) || c == '_';
+}
+
+static inline struct expression_variable *
+expression_variable(struct expression_context *ctx, const char *name)
+{
+ for (size_t i = 0; i < ctx->variables.size; ++i) {
+ struct expression_variable *v = vec_at(&ctx->variables, i);
+ if (strcmp(v->name, name) == 0) {
+ return v;
+ }
+ }
+ return NULL;
+}
+
+static inline bool expression_set_variable(struct expression_context *ctx,
+ const char *name,
+ double value)
+{
+ if (!expression_name_start((unsigned char)name[0]) ||
+ strlen(name) >= EXPRESSION_NAME_MAX) return false;
+ for (const unsigned char *p = (const unsigned char *)name + 1; *p; ++p) {
+ if (!expression_name_char(*p)) {
+ return false;
+ }
+ }
+ struct expression_variable *v = expression_variable(ctx, name);
+ if (!v) {
+ struct expression_variable item = { .value = value, .assigned = true };
+ strcpy(item.name, name);
+ if (!vec_push_back(&ctx->variables, &item)) {
+ return false;
+ }
+ } else {
+ v->value = value;
+ v->assigned = true;
+ }
+ return true;
+}
+
+static inline bool expression_collect_variables(struct expression_context *ctx,
+ const char *text,
+ struct expression_error *error)
+{
+ vec_clear(&ctx->variables);
+ for (size_t i = 0; text[i];) {
+ unsigned char c = (unsigned char)text[i];
+ if (isspace(c)) {
+ ++i;
+ } else if (expression_name_start(c)) {
+ size_t start = i++;
+ while (expression_name_char((unsigned char)text[i])) {
+ ++i;
+ }
+ size_t length = i - start;
+ if (length >= EXPRESSION_NAME_MAX) {
+ expression_error(error, EXPRESSION_SYNTAX_ERROR, start);
+ return false;
+ }
+ char name[EXPRESSION_NAME_MAX];
+ memcpy(name, text + start, length);
+ name[length] = '\0';
+ if (!expression_variable(ctx, name)) {
+ struct expression_variable item = { .assigned = false };
+ strcpy(item.name, name);
+ if (!vec_push_back(&ctx->variables, &item)) {
+ expression_error(error, EXPRESSION_SYNTAX_ERROR, start);
+ return false;
+ }
+ }
+ } else if (isdigit(c) || c == '.' || c == '+' || c == '-' ||
+ c == '*' || c == '/' || c == '(' || c == ')') {
+ ++i;
+ } else {
+ expression_error(error, EXPRESSION_SYNTAX_ERROR, i);
+ return false;
+ }
+ }
+ expression_error(error, EXPRESSION_OK, 0);
+ return true;
+}
+
+/** @brief An operator and its byte offset in the expression. */
+struct expression_operator {
+ char symbol;
+ size_t position;
+};
+
+/** @brief Returns precedence; `p` and `n` denote unary plus and minus. */
+static inline int expression_precedence(char symbol)
+{
+ switch (symbol) {
+ case 'p': case 'n': return 3;
+ case '*': case '/': return 2;
+ case '+': case '-': return 1;
+ default: return 0;
+ }
+}
+
+/** @brief Applies the top operator to the value stack. */
+static inline bool expression_apply(stack *values, stack *operators,
+ struct expression_error *error)
+{
+ struct expression_operator op = *stack_top_as(operators, struct expression_operator);
+ stack_pop(operators);
+ bool unary = op.symbol == 'p' || op.symbol == 'n';
+ if (stack_size(values) < (unary ? 1u : 2u)) {
+ expression_error(error, EXPRESSION_SYNTAX_ERROR, op.position);
+ return false;
+ }
+ if (unary) {
+ if (op.symbol == 'n') *stack_top_as(values, double) *= -1;
+ return true;
+ }
+ double rhs = *stack_top_as(values, double);
+ stack_pop(values);
+ double *lhs = stack_top_as(values, double);
+ switch (op.symbol) {
+ case '+': *lhs += rhs; break;
+ case '-': *lhs -= rhs; break;
+ case '*': *lhs *= rhs; break;
+ case '/':
+ if (rhs == 0.0) {
+ expression_error(error, EXPRESSION_DIVISION_BY_ZERO, op.position);
+ return false;
+ }
+ *lhs /= rhs;
+ break;
+ }
+ return true;
+}
+
+/**
+ * @brief Evaluates an expression using value and operator stacks.
+ * @param ctx The variable table used for name lookup.
+ * @param text The null-terminated expression.
+ * @param result Receives the value on success; unchanged on failure.
+ * @param error Receives the error code and byte offset, or may be null.
+ * @return `true` on success; otherwise `false`.
+ * @pre `ctx` is initialized; `text` and `result` are valid pointers.
+ */
+static inline bool expression_eval(const struct expression_context *ctx,
+ const char *text,
+ double *result,
+ struct expression_error *error)
+{
+ STACK(values, double);
+ STACK(operators, struct expression_operator);
+ bool expect_operand = true;
+ bool success = false;
+ size_t position = 0;
+
+ while (text[position]) {
+ unsigned char c = (unsigned char)text[position];
+ if (isspace(c)) {
+ ++position;
+ continue;
+ }
+ if (isdigit(c) || c == '.' || expression_name_start(c)) {
+ if (!expect_operand) goto syntax_error;
+ double value;
+ if (expression_name_start(c)) {
+ size_t start = position++;
+ while (expression_name_char((unsigned char)text[position])) ++position;
+ size_t length = position - start;
+ if (length >= EXPRESSION_NAME_MAX) {
+ position = start;
+ goto syntax_error;
+ }
+ const struct expression_variable *variable = NULL;
+ for (size_t i = 0; i < ctx->variables.size; ++i) {
+ const struct expression_variable *v = vec_at_const(&ctx->variables, i);
+ if (strlen(v->name) == length &&
+ strncmp(v->name, text + start, length) == 0) {
+ variable = v;
+ break;
+ }
+ }
+ if (!variable || !variable->assigned) {
+ expression_error(error, variable ? EXPRESSION_UNSET_VARIABLE
+ : EXPRESSION_UNKNOWN_VARIABLE, start);
+ goto done;
+ }
+ value = variable->value;
+ } else {
+ char *end;
+ errno = 0;
+ value = strtod(text + position, &end);
+ if (end == text + position || errno == ERANGE) goto syntax_error;
+ position = (size_t)(end - text);
+ }
+ if (!stack_push(&values, &value)) goto syntax_error;
+ expect_operand = false;
+ continue;
+ }
+ struct expression_operator op = { .symbol = (char)c, .position = position };
+ if (c == '(') {
+ if (!expect_operand) goto syntax_error;
+ } else if (c == ')') {
+ if (expect_operand) goto syntax_error;
+ while (!stack_empty(&operators) &&
+ stack_top_as(&operators, struct expression_operator)->symbol != '(') {
+ if (!expression_apply(&values, &operators, error)) goto done;
+ }
+ if (stack_empty(&operators)) goto paren_error;
+ stack_pop(&operators);
+ ++position;
+ continue;
+ } else if (c == '+' || c == '-' || c == '*' || c == '/') {
+ if (expect_operand) {
+ if (c != '+' && c != '-') goto syntax_error;
+ op.symbol = c == '+' ? 'p' : 'n';
+ } else {
+ while (!stack_empty(&operators) &&
+ expression_precedence(stack_top_as(&operators,
+ struct expression_operator)->symbol) >= expression_precedence(op.symbol)) {
+ if (!expression_apply(&values, &operators, error)) goto done;
+ }
+ }
+ } else {
+ goto syntax_error;
+ }
+ if (!stack_push(&operators, &op)) goto syntax_error;
+ expect_operand = true;
+ ++position;
+ }
+ if (expect_operand) goto syntax_error;
+ while (!stack_empty(&operators)) {
+ struct expression_operator op = *stack_top_as(&operators, struct expression_operator);
+ if (op.symbol == '(') {
+ position = op.position;
+ goto paren_error;
+ }
+ if (!expression_apply(&values, &operators, error)) goto done;
+ }
+ if (stack_size(&values) != 1) goto syntax_error;
+ *result = *stack_top_as(&values, double);
+ expression_error(error, EXPRESSION_OK, 0);
+ success = true;
+ goto done;
+
+syntax_error:
+ expression_error(error, EXPRESSION_SYNTAX_ERROR, position);
+ goto done;
+paren_error:
+ expression_error(error, EXPRESSION_PAREN_ERROR, position);
+done:
+ stack_destroy(&values);
+ stack_destroy(&operators);
+ return success;
+}
+
+#endif /* EXPRESSION_H */