diff options
| -rw-r--r-- | lab04/expression.h | 438 | ||||
| -rw-r--r-- | lab04/tests/test.c | 315 | ||||
| -rw-r--r-- | lab04/ui.c | 96 |
3 files changed, 771 insertions, 78 deletions
diff --git a/lab04/expression.h b/lab04/expression.h index f0e2847..234ecb3 100644 --- a/lab04/expression.h +++ b/lab04/expression.h @@ -4,6 +4,7 @@ #include <ctype.h> #include <errno.h> +#include <math.h> #include <stdbool.h> #include <stddef.h> #include <stdlib.h> @@ -12,6 +13,7 @@ #include "stack.h" #define EXPRESSION_NAME_MAX 64 +#define EXPRESSION_TEXT_MAX 256 enum expression_error_code { EXPRESSION_OK, @@ -20,6 +22,7 @@ enum expression_error_code { EXPRESSION_UNKNOWN_VARIABLE, EXPRESSION_UNSET_VARIABLE, EXPRESSION_DIVISION_BY_ZERO, + EXPRESSION_UNKNOWN_FUNCTION, }; struct expression_error { @@ -33,18 +36,27 @@ struct expression_variable { bool assigned; }; +struct expression_function { + char name[EXPRESSION_NAME_MAX]; + char parameter[EXPRESSION_NAME_MAX]; + char body[EXPRESSION_TEXT_MAX]; +}; + struct expression_context { struct vec variables; + struct vec functions; }; static inline void expression_init(struct expression_context *ctx) { vec_init(&ctx->variables, struct expression_variable); + vec_init(&ctx->functions, struct expression_function); } static inline void expression_destroy(struct expression_context *ctx) { vec_destroy(&ctx->variables); + vec_destroy(&ctx->functions); } static inline void expression_error(struct expression_error *error, @@ -67,6 +79,27 @@ static inline bool expression_name_char(unsigned char c) return isalnum(c) || c == '_'; } +static inline size_t expression_skip_space(const char *text, size_t position) +{ + while (isspace((unsigned char)text[position])) ++position; + return position; +} + +static inline bool expression_read_name(const char *text, size_t *position, + char name[EXPRESSION_NAME_MAX]) +{ + size_t start = *position; + if (!expression_name_start((unsigned char)text[start])) return false; + size_t end = start + 1; + while (expression_name_char((unsigned char)text[end])) ++end; + size_t length = end - start; + if (length >= EXPRESSION_NAME_MAX) return false; + memcpy(name, text + start, length); + name[length] = '\0'; + *position = end; + return true; +} + static inline struct expression_variable * expression_variable(struct expression_context *ctx, const char *name) { @@ -108,57 +141,159 @@ static inline bool expression_collect_variables(struct expression_context *ctx, const char *text, struct expression_error *error) { - vec_clear(&ctx->variables); + VEC(variables, struct expression_variable); for (size_t i = 0; text[i];) { unsigned char c = (unsigned char)text[i]; if (isspace(c)) { ++i; + } else if (isdigit(c) || c == '.') { + char *end; + errno = 0; + strtod(text + i, &end); + if (end == text + i || errno == ERANGE) goto syntax_error; + i = (size_t)(end - text); } 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)) { + if (!expression_read_name(text, &i, name)) goto syntax_error; + if (text[expression_skip_space(text, i)] == '(') continue; + size_t index = 0; + while (index < variables.size && + strcmp(((struct expression_variable *)vec_at(&variables, index))->name, + name) != 0) ++index; + if (index == variables.size) { 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; - } + if (!vec_push_back(&variables, &item)) goto syntax_error; } - } else if (isdigit(c) || c == '.' || c == '+' || c == '-' || - c == '*' || c == '/' || c == '(' || c == ')') { + } else if (c == '<' || c == '>') { + i += text[i + 1] == '=' ? 2 : 1; + } else if ((c == '=' || c == '!') && text[i + 1] == '=') { + i += 2; + } else if (strchr("+-*/^()?:", c)) { ++i; } else { - expression_error(error, EXPRESSION_SYNTAX_ERROR, i); + goto syntax_error; + } + continue; +syntax_error: + expression_error(error, EXPRESSION_SYNTAX_ERROR, i); + vec_destroy(&variables); + return false; + } + vec_swap(&ctx->variables, &variables); + vec_destroy(&variables); + expression_error(error, EXPRESSION_OK, 0); + return true; +} + +static inline bool expression_define(struct expression_context *ctx, + const char *text, + struct expression_error *error) +{ + struct expression_function function; + size_t position = expression_skip_space(text, 0); + if (strncmp(text + position, "DEF", 3) != 0 || + expression_name_char((unsigned char)text[position + 3])) goto syntax_error; + position = expression_skip_space(text, position + 3); + if (!expression_read_name(text, &position, function.name)) goto syntax_error; + position = expression_skip_space(text, position); + if (text[position] != '(') goto syntax_error; + position = expression_skip_space(text, position + 1); + if (!expression_read_name(text, &position, function.parameter)) goto syntax_error; + position = expression_skip_space(text, position); + if (text[position] != ')') goto syntax_error; + position = expression_skip_space(text, position + 1); + if (text[position] != '=') goto syntax_error; + position = expression_skip_space(text, position + 1); + + size_t end = strlen(text); + while (end > position && isspace((unsigned char)text[end - 1])) --end; + if (end > position && text[end - 1] == ';') --end; + while (end > position && isspace((unsigned char)text[end - 1])) --end; + size_t length = end - position; + if (length == 0 || length >= sizeof(function.body)) goto syntax_error; + memcpy(function.body, text + position, length); + function.body[length] = '\0'; + + struct expression_context body; + expression_init(&body); + if (!expression_collect_variables(&body, function.body, error)) { + if (error) error->position += position; + expression_destroy(&body); + return false; + } + for (size_t i = 0; i < body.variables.size; ++i) { + const struct expression_variable *variable = vec_at_const(&body.variables, i); + if (strcmp(variable->name, function.parameter) != 0) { + expression_error(error, EXPRESSION_UNKNOWN_VARIABLE, position); + expression_destroy(&body); return false; } } + expression_destroy(&body); + if (!vec_push_back(&ctx->functions, &function)) goto syntax_error; expression_error(error, EXPRESSION_OK, 0); return true; + +syntax_error: + expression_error(error, EXPRESSION_SYNTAX_ERROR, position); + return false; +} + +static inline size_t expression_find_function(const struct expression_context *ctx, + const char *name, + size_t current_function) +{ + if (current_function != SIZE_MAX) { + const struct expression_function *function = + vec_at_const(&ctx->functions, current_function); + if (strcmp(function->name, name) == 0) return current_function; + } + for (size_t i = ctx->functions.size; i-- > 0;) { + const struct expression_function *function = vec_at_const(&ctx->functions, i); + if (strcmp(function->name, name) == 0) return i; + } + return SIZE_MAX; } +enum expression_operator_kind { + EXPRESSION_OPEN, + EXPRESSION_POSITIVE, + EXPRESSION_NEGATIVE, + EXPRESSION_ADD, + EXPRESSION_SUBTRACT, + EXPRESSION_MULTIPLY, + EXPRESSION_DIVIDE, + EXPRESSION_POWER, + EXPRESSION_LESS, + EXPRESSION_LESS_EQUAL, + EXPRESSION_GREATER, + EXPRESSION_GREATER_EQUAL, + EXPRESSION_EQUAL, + EXPRESSION_NOT_EQUAL, + EXPRESSION_CONDITIONAL, +}; + /** @brief An operator and its byte offset in the expression. */ struct expression_operator { - char symbol; + enum expression_operator_kind kind; size_t position; + size_t function_index; + size_t branch_end; + size_t resume; + size_t previous_branch; }; -/** @brief Returns precedence; `p` and `n` denote unary plus and minus. */ -static inline int expression_precedence(char symbol) +static inline int expression_precedence(enum expression_operator_kind kind) { - switch (symbol) { - case 'p': case 'n': return 3; - case '*': case '/': return 2; - case '+': case '-': return 1; + switch (kind) { + case EXPRESSION_POWER: return 7; + case EXPRESSION_POSITIVE: case EXPRESSION_NEGATIVE: return 6; + case EXPRESSION_MULTIPLY: case EXPRESSION_DIVIDE: return 5; + case EXPRESSION_ADD: case EXPRESSION_SUBTRACT: return 4; + case EXPRESSION_LESS: case EXPRESSION_LESS_EQUAL: + case EXPRESSION_GREATER: case EXPRESSION_GREATER_EQUAL: return 3; + case EXPRESSION_EQUAL: case EXPRESSION_NOT_EQUAL: return 2; default: return 0; } } @@ -169,29 +304,75 @@ static inline bool expression_apply(stack *values, stack *operators, { struct expression_operator op = *stack_top_as(operators, struct expression_operator); stack_pop(operators); - bool unary = op.symbol == 'p' || op.symbol == 'n'; + bool unary = op.kind == EXPRESSION_POSITIVE || op.kind == EXPRESSION_NEGATIVE; 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; + if (op.kind == EXPRESSION_NEGATIVE) *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 '/': + switch (op.kind) { + case EXPRESSION_ADD: *lhs += rhs; break; + case EXPRESSION_SUBTRACT: *lhs -= rhs; break; + case EXPRESSION_MULTIPLY: *lhs *= rhs; break; + case EXPRESSION_DIVIDE: if (rhs == 0.0) { expression_error(error, EXPRESSION_DIVISION_BY_ZERO, op.position); return false; } *lhs /= rhs; break; + case EXPRESSION_POWER: *lhs = pow(*lhs, rhs); break; + case EXPRESSION_LESS: *lhs = *lhs < rhs; break; + case EXPRESSION_LESS_EQUAL: *lhs = *lhs <= rhs; break; + case EXPRESSION_GREATER: *lhs = *lhs > rhs; break; + case EXPRESSION_GREATER_EQUAL: *lhs = *lhs >= rhs; break; + case EXPRESSION_EQUAL: *lhs = *lhs == rhs; break; + case EXPRESSION_NOT_EQUAL: *lhs = *lhs != rhs; break; + default: break; + } + return true; +} + +/* Find the matching colon and the end of the false branch without executing it. */ +static inline bool expression_branch_bounds(const char *text, size_t question, + size_t *colon, size_t *end, + struct expression_error *error) +{ + size_t parentheses = 0; + size_t questions = 0; + size_t position = question + 1; + *colon = SIZE_MAX; + for (; text[position]; ++position) { + char c = text[position]; + if (c == '(') { + ++parentheses; + } else if (c == ')') { + if (parentheses == 0) break; + --parentheses; + } else if (parentheses == 0 && c == '?') { + ++questions; + } else if (parentheses == 0 && c == ':') { + if (questions != 0) --questions; + else if (*colon == SIZE_MAX) *colon = position; + else break; + } + } + *end = position; + if (parentheses != 0) { + expression_error(error, EXPRESSION_PAREN_ERROR, position); + return false; + } + if (*colon == SIZE_MAX || questions != 0 || + expression_skip_space(text, question + 1) == *colon || + expression_skip_space(text, *colon + 1) == *end) { + expression_error(error, EXPRESSION_SYNTAX_ERROR, question); + return false; } return true; } @@ -205,49 +386,85 @@ static inline bool expression_apply(stack *values, stack *operators, * @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) +static inline bool __expression_eval(const struct expression_context *ctx, + const char *text, + size_t current_function, + double argument, + 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; + size_t branch = SIZE_MAX; - while (text[position]) { - unsigned char c = (unsigned char)text[position]; - if (isspace(c)) { - ++position; - continue; + for (;;) { + position = expression_skip_space(text, position); + if (branch != SIZE_MAX) { + struct expression_operator boundary = + vec_at_as(&operators.container, struct expression_operator, branch); + if (position == boundary.branch_end) { + if (expect_operand) goto syntax_error; + while (stack_size(&operators) > branch + 1) { + if (!expression_apply(&values, &operators, error)) goto done; + } + stack_pop(&operators); + branch = boundary.previous_branch; + position = boundary.resume; + continue; + } } + unsigned char c = (unsigned char)text[position]; + if (c == '\0') break; 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; + size_t start = position; + char name[EXPRESSION_NAME_MAX]; + if (!expression_read_name(text, &position, name)) goto syntax_error; + size_t next = expression_skip_space(text, position); + if (text[next] == '(') { + size_t index = expression_find_function(ctx, name, current_function); + if (index == SIZE_MAX) { + expression_error(error, EXPRESSION_UNKNOWN_FUNCTION, start); + goto done; } + struct expression_operator call = { + .kind = EXPRESSION_OPEN, + .position = next, + .function_index = index, + }; + if (!stack_push(&operators, &call)) goto syntax_error; + position = next + 1; + continue; } - if (!variable || !variable->assigned) { - expression_error(error, variable ? EXPRESSION_UNSET_VARIABLE - : EXPRESSION_UNKNOWN_VARIABLE, start); - goto done; + if (current_function != SIZE_MAX) { + const struct expression_function *function = + vec_at_const(&ctx->functions, current_function); + if (strcmp(function->parameter, name) != 0) { + expression_error(error, EXPRESSION_UNKNOWN_VARIABLE, start); + goto done; + } + value = argument; + } else { + 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 (strcmp(v->name, name) == 0) { + variable = v; + break; + } + } + if (!variable || !variable->assigned) { + expression_error(error, variable ? EXPRESSION_UNSET_VARIABLE + : EXPRESSION_UNKNOWN_VARIABLE, start); + goto done; + } + value = variable->value; } - value = variable->value; } else { char *end; errno = 0; @@ -259,41 +476,98 @@ static inline bool expression_eval(const struct expression_context *ctx, expect_operand = false; continue; } - struct expression_operator op = { .symbol = (char)c, .position = position }; + struct expression_operator op = { + .position = position, + .function_index = SIZE_MAX, + }; + size_t length = 1; if (c == '(') { if (!expect_operand) goto syntax_error; + op.kind = EXPRESSION_OPEN; } else if (c == ')') { if (expect_operand) goto syntax_error; while (!stack_empty(&operators) && - stack_top_as(&operators, struct expression_operator)->symbol != '(') { + stack_top_as(&operators, struct expression_operator)->kind != EXPRESSION_OPEN) { if (!expression_apply(&values, &operators, error)) goto done; } if (stack_empty(&operators)) goto paren_error; + struct expression_operator open = + *stack_top_as(&operators, struct expression_operator); stack_pop(&operators); + if (open.function_index != SIZE_MAX) { + const struct expression_function *function = + vec_at_const(&ctx->functions, open.function_index); + double value; + if (!__expression_eval(ctx, function->body, open.function_index, + *stack_top_as(&values, double), &value, error)) goto done; + *stack_top_as(&values, double) = value; + } ++position; continue; - } else if (c == '+' || c == '-' || c == '*' || c == '/') { + } else if (c == '?') { + if (expect_operand) goto syntax_error; + while (!stack_empty(&operators) && + expression_precedence(stack_top_as(&operators, + struct expression_operator)->kind) != 0) { + if (!expression_apply(&values, &operators, error)) goto done; + } + size_t colon, end; + if (!expression_branch_bounds(text, position, &colon, &end, error)) goto done; + bool condition = *stack_top_as(&values, double) != 0.0; + stack_pop(&values); + op.kind = EXPRESSION_CONDITIONAL; + op.branch_end = condition ? colon : end; + op.resume = end; + op.previous_branch = branch; + size_t index = stack_size(&operators); + if (!stack_push(&operators, &op)) goto syntax_error; + branch = index; + position = condition ? position + 1 : colon + 1; + expect_operand = true; + continue; + } else { + switch (c) { + case '+': op.kind = EXPRESSION_ADD; break; + case '-': op.kind = EXPRESSION_SUBTRACT; break; + case '*': op.kind = EXPRESSION_MULTIPLY; break; + case '/': op.kind = EXPRESSION_DIVIDE; break; + case '^': op.kind = EXPRESSION_POWER; break; + case '<': + op.kind = text[position + 1] == '=' ? EXPRESSION_LESS_EQUAL : EXPRESSION_LESS; + length = text[position + 1] == '=' ? 2 : 1; + break; + case '>': + op.kind = text[position + 1] == '=' ? EXPRESSION_GREATER_EQUAL : EXPRESSION_GREATER; + length = text[position + 1] == '=' ? 2 : 1; + break; + case '=': case '!': + if (text[position + 1] != '=') goto syntax_error; + op.kind = c == '=' ? EXPRESSION_EQUAL : EXPRESSION_NOT_EQUAL; + length = 2; + break; + default: goto syntax_error; + } if (expect_operand) { if (c != '+' && c != '-') goto syntax_error; - op.symbol = c == '+' ? 'p' : 'n'; + op.kind = c == '+' ? EXPRESSION_POSITIVE : EXPRESSION_NEGATIVE; } else { - while (!stack_empty(&operators) && - expression_precedence(stack_top_as(&operators, - struct expression_operator)->symbol) >= expression_precedence(op.symbol)) { + while (!stack_empty(&operators)) { + int top = expression_precedence(stack_top_as(&operators, + struct expression_operator)->kind); + int current = expression_precedence(op.kind); + if (top < current || (top == current && op.kind == EXPRESSION_POWER)) break; if (!expression_apply(&values, &operators, error)) goto done; } } - } else { - goto syntax_error; } if (!stack_push(&operators, &op)) goto syntax_error; expect_operand = true; - ++position; + position += length; } if (expect_operand) goto syntax_error; while (!stack_empty(&operators)) { struct expression_operator op = *stack_top_as(&operators, struct expression_operator); - if (op.symbol == '(') { + if (op.kind == EXPRESSION_OPEN) { position = op.position; goto paren_error; } @@ -316,4 +590,22 @@ done: return success; } +static inline bool expression_eval(const struct expression_context *ctx, + const char *text, + double *result, + struct expression_error *error) +{ + return __expression_eval(ctx, text, SIZE_MAX, 0.0, result, error); +} + +static inline bool expression_call(const struct expression_context *ctx, + size_t function_index, double argument, + double *result, + struct expression_error *error) +{ + const struct expression_function *function = + vec_at_const(&ctx->functions, function_index); + return __expression_eval(ctx, function->body, function_index, argument, result, error); +} + #endif /* EXPRESSION_H */ diff --git a/lab04/tests/test.c b/lab04/tests/test.c index c10b130..6ce4c7e 100644 --- a/lab04/tests/test.c +++ b/lab04/tests/test.c @@ -447,6 +447,309 @@ static bool test_expression_error_paths(void) return true; } +static bool test_expression_power_precedence(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + const struct { + const char *text; + double expected; + } cases[] = { + { "2^3^2", 512.0 }, + { "-2^2", -4.0 }, + { "(-2)^2", 4.0 }, + { "2^-2", 0.25 }, + { "2^-2^2", 0.0625 }, + { "--2^2", 4.0 }, + { "2^--2", 4.0 }, + { "-2^-2", -0.25 }, + { "2^-2*3", 0.75 }, + { "1+2*3^2", 19.0 }, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + CHECK(expression_eval(&context, cases[i].text, &result, &error)); + CHECK(fabs(result - cases[i].expected) < 1e-12); + } + expression_destroy(&context); + return true; +} + +static bool test_expression_scientific_literals_and_variables(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + const struct { + const char *text; + double expected; + } cases[] = { + { "1.14e2", 114.0 }, + { "-1.14e2", -114.0 }, + { "2E+1+.5", 20.5 }, + { "1e-3+5e-1", 0.501 }, + { "1.+.5", 1.5 }, + { "+2.5e-2", 0.025 }, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + CHECK(expression_collect_variables(&context, cases[i].text, &error)); + CHECK(context.variables.size == 0); + CHECK(expression_eval(&context, cases[i].text, &result, &error)); + CHECK(fabs(result - cases[i].expected) < 1e-12); + } + CHECK(expression_collect_variables(&context, "1.14e2+X2*X2-_value", &error)); + CHECK(context.variables.size == 2); + CHECK(expression_set_variable(&context, "X2", 3.0)); + CHECK(expression_set_variable(&context, "_value", 2.0)); + CHECK(expression_eval(&context, "1.14e2+X2*X2-_value", &result, &error)); + CHECK(result == 121.0); + expression_destroy(&context); + return true; +} + +static bool test_expression_comparisons(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + const struct { + const char *text; + double expected; + } cases[] = { + { "1<2", 1.0 }, { "2<1", 0.0 }, + { "1<=1", 1.0 }, { "2<=1", 0.0 }, + { "2>1", 1.0 }, { "1>2", 0.0 }, + { "2>=2", 1.0 }, { "1>=2", 0.0 }, + { "2==2", 1.0 }, { "1==2", 0.0 }, + { "1!=2", 1.0 }, { "2!=2", 0.0 }, + { "1+2*3>=7", 1.0 }, + { "2^3>2*3+1", 1.0 }, + { "1<2==1", 1.0 }, + { "1==2<3", 1.0 }, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + CHECK(expression_eval(&context, cases[i].text, &result, &error)); + CHECK(result == cases[i].expected); + } + expression_destroy(&context); + return true; +} + +static bool test_expression_conditional_nesting(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + const struct { + const char *text; + double expected; + } cases[] = { + { "0?8:9", 9.0 }, + { "-2?8:9", 8.0 }, + { "3<4?10:20", 10.0 }, + { "1?0:1?2:3", 0.0 }, + { "1?0?2:3:4", 3.0 }, + { "0?1?2:3:4", 4.0 }, + { "0?2:1?3:4", 3.0 }, + { "(1?2:3)?4:5", 4.0 }, + { "2*(1?3:4)+5", 11.0 }, + { "(0?1:1?2:3)*4", 8.0 }, + { "2^(1?2:3)", 4.0 }, + { "(-1?2:3)^2", 4.0 }, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + CHECK(expression_eval(&context, cases[i].text, &result, &error)); + CHECK(result == cases[i].expected); + } + expression_destroy(&context); + return true; +} + +static bool test_expression_conditional_short_circuit(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + CHECK(expression_collect_variables(&context, "X+1", &error)); + const char *cases[] = { + "0?1/0:7", + "1?7:1/0", + "0?X:7", + "1?7:X", + "0?missing(1):7", + "1?7:missing(1)", + "1?(0?1/0:6)+1:1/0", + "0?missing(X):(1?7:1/0)", + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + CHECK(expression_eval(&context, cases[i], &result, &error)); + CHECK(result == 7.0); + } + expression_destroy(&context); + return true; +} + +static bool test_expression_function_calls(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + CHECK(expression_define(&context, "DEF f(X2)=3+4*X2;", &error)); + CHECK(expression_define(&context, "DEF g(x)=3+4*f(x);", &error)); + const struct { + const char *text; + double expected; + } cases[] = { + { "f(5)", 23.0 }, + { "g(5)", 95.0 }, + { "f(2+3)", 23.0 }, + { "f(f(1))", 31.0 }, + { "f(g(1))", 127.0 }, + { "-f(1)^2", -49.0 }, + { "f(1?5:1/0)", 23.0 }, + { "f(1?2:1/0)+g(0?1/0:1)", 42.0 }, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + CHECK(expression_eval(&context, cases[i].text, &result, &error)); + CHECK(result == cases[i].expected); + } + CHECK(expression_collect_variables(&context, "f(X2)+f(X2)", &error)); + CHECK(context.variables.size == 1); + CHECK(expression_set_variable(&context, "X2", 5.0)); + CHECK(expression_eval(&context, "f(X2)+f(X2)", &result, &error)); + CHECK(result == 46.0); + CHECK(expression_eval(&context, "X2", &result, &error)); + CHECK(result == 5.0); + expression_destroy(&context); + return true; +} + +static bool test_expression_recursive_calls(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + CHECK(expression_define(&context, "DEF fact(n)=n<=1?1:n*fact(n-1);", &error)); + CHECK(expression_eval(&context, "fact(0)", &result, &error)); + CHECK(result == 1.0); + CHECK(expression_eval(&context, "fact(5)", &result, &error)); + CHECK(result == 120.0); + CHECK(expression_define(&context, "DEF fib(n)=n<=1?n:fib(n-1)+fib(n-2);", &error)); + CHECK(expression_eval(&context, "fib(10)", &result, &error)); + CHECK(result == 55.0); + CHECK(expression_define(&context, "DEF even(n)=n==0?1:odd(n-1);", &error)); + CHECK(expression_define(&context, "DEF odd(n)=n==0?0:even(n-1);", &error)); + CHECK(expression_eval(&context, "even(8)+odd(9)", &result, &error)); + CHECK(result == 2.0); + expression_destroy(&context); + return true; +} + +static bool test_expression_function_history(void) +{ + struct expression_context context; + struct expression_error error; + double result; + expression_init(&context); + CHECK(expression_define(&context, "DEF f(X2)=3+4*X2;", &error)); + CHECK(expression_define(&context, "DEF fact(n)=n<=1?1:n*fact(n-1);", &error)); + CHECK(expression_define(&context, "DEF f(X2)=100+X2;", &error)); + CHECK(expression_define(&context, "DEF fact(n)=100+n;", &error)); + CHECK(context.functions.size == 4); + CHECK(expression_eval(&context, "f(5)", &result, &error)); + CHECK(result == 105.0); + CHECK(expression_eval(&context, "fact(5)", &result, &error)); + CHECK(result == 105.0); + CHECK(expression_call(&context, 0, 5.0, &result, &error)); + CHECK(result == 23.0); + CHECK(expression_call(&context, 1, 5.0, &result, &error)); + CHECK(result == 120.0); + CHECK(expression_call(&context, 2, 5.0, &result, &error)); + CHECK(result == 105.0); + CHECK(expression_call(&context, 3, 5.0, &result, &error)); + CHECK(result == 105.0); + expression_destroy(&context); + return true; +} + +static bool test_expression_definition_errors(void) +{ + struct expression_context context; + struct expression_error error; + expression_init(&context); + CHECK(expression_define(&context, "DEF f(x)=x+1;", &error)); + const struct { + const char *text; + enum expression_error_code expected; + } cases[] = { + { "", EXPRESSION_SYNTAX_ERROR }, + { "DEF", EXPRESSION_SYNTAX_ERROR }, + { "DEF f()=1;", EXPRESSION_SYNTAX_ERROR }, + { "DEF 2f(x)=x;", EXPRESSION_SYNTAX_ERROR }, + { "DEF f(1)=1;", EXPRESSION_SYNTAX_ERROR }, + { "DEF f(x,y)=x;", EXPRESSION_SYNTAX_ERROR }, + { "DEF f(x) x;", EXPRESSION_SYNTAX_ERROR }, + { "DEF f(x)=;", EXPRESSION_SYNTAX_ERROR }, + { "DEF f(x)=x@2;", EXPRESSION_SYNTAX_ERROR }, + { "DEF f(x)=1e999;", EXPRESSION_SYNTAX_ERROR }, + { "DEF f(x)=x+y;", EXPRESSION_UNKNOWN_VARIABLE }, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + CHECK(!expression_define(&context, cases[i].text, &error)); + CHECK(error.code == cases[i].expected); + CHECK(context.functions.size == 1); + } + expression_destroy(&context); + return true; +} + +static bool test_expression_conditional_and_call_errors(void) +{ + struct expression_context context; + struct expression_error error; + expression_init(&context); + CHECK(expression_define(&context, "DEF f(x)=x+1;", &error)); + const struct { + const char *text; + enum expression_error_code expected; + } cases[] = { + { "missing(1)", EXPRESSION_UNKNOWN_FUNCTION }, + { "f(Y)", EXPRESSION_UNKNOWN_VARIABLE }, + { "f()", EXPRESSION_SYNTAX_ERROR }, + { "f(1,2)", EXPRESSION_SYNTAX_ERROR }, + { "f(1", EXPRESSION_PAREN_ERROR }, + { "1?", EXPRESSION_SYNTAX_ERROR }, + { "1?:2", EXPRESSION_SYNTAX_ERROR }, + { "0?1:", EXPRESSION_SYNTAX_ERROR }, + { "1?2:3:4", EXPRESSION_SYNTAX_ERROR }, + { "1:2", EXPRESSION_SYNTAX_ERROR }, + { "1<", EXPRESSION_SYNTAX_ERROR }, + { "1<=", EXPRESSION_SYNTAX_ERROR }, + { "1=1", EXPRESSION_SYNTAX_ERROR }, + { "1!1", EXPRESSION_SYNTAX_ERROR }, + { "1?(2:3", EXPRESSION_PAREN_ERROR }, + { "1?2:3?4", EXPRESSION_SYNTAX_ERROR }, + { "0?1?2:3", EXPRESSION_SYNTAX_ERROR }, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + double result = 123.0; + CHECK(!expression_eval(&context, cases[i].text, &result, &error)); + CHECK(error.code == cases[i].expected); + CHECK(result == 123.0); + } + /* The last case is missing the colon for the outer question mark. */ + CHECK(error.position == 1); + expression_destroy(&context); + return true; +} + struct test_case { const char *name; bool (*function)(void); @@ -477,6 +780,18 @@ int main(void) { "expression deep parentheses and associativity", test_expression_deep_parentheses_and_associativity }, { "expression error paths", test_expression_error_paths }, + { "expression power precedence", test_expression_power_precedence }, + { "expression scientific literals and variables", + test_expression_scientific_literals_and_variables }, + { "expression comparisons", test_expression_comparisons }, + { "expression conditional nesting", test_expression_conditional_nesting }, + { "expression conditional short circuit", test_expression_conditional_short_circuit }, + { "expression function calls", test_expression_function_calls }, + { "expression recursive calls", test_expression_recursive_calls }, + { "expression function history", test_expression_function_history }, + { "expression definition errors", test_expression_definition_errors }, + { "expression conditional and call errors", + test_expression_conditional_and_call_errors }, }; size_t failures = 0; @@ -55,7 +55,7 @@ struct ui_state { bool poly_list_result_valid; struct expression_context expression; - char expression_text[256]; + char expression_text[EXPRESSION_TEXT_MAX]; double expression_result; bool expression_result_valid; @@ -1340,6 +1340,7 @@ static const char *ui_expression_error_text(enum expression_error_code code) case EXPRESSION_UNKNOWN_VARIABLE: return "变量不存在"; case EXPRESSION_UNSET_VARIABLE: return "变量尚未赋值"; case EXPRESSION_DIVISION_BY_ZERO: return "除数不能为零"; + case EXPRESSION_UNKNOWN_FUNCTION: return "函数不存在"; default: return "表达式错误"; } } @@ -1347,9 +1348,10 @@ static const char *ui_expression_error_text(enum expression_error_code code) static void ui_draw_expression(const struct ui_state *state, size_t selected) { static const char *const items[] = { - "输入新表达式", + "输入表达式或 DEF/RUN 命令", "修改变量值", "计算表达式", + "查看并运行历史函数", }; tui_clear(); @@ -1369,18 +1371,43 @@ static void ui_draw_expression(const struct ui_state *state, size_t selected) printf("\n" COLOR_CYAN "计算结果:" STYLE_RESET "\n"); if (state->expression_result_valid) printf(" %g\n", state->expression_result); else printf(" (未计算)\n"); - ui_print_menu(items, 3, selected, "返回"); + ui_print_menu(items, 4, selected, "返回"); ui_print_status(state); ui_print_footer("↑/↓ 或 j/k 选择 Enter 确认 q/Esc 返回"); fflush(stdout); } +static void ui_expression_calculate(struct ui_state *state); + static void ui_expression_input(struct ui_state *state) { char input[sizeof(state->expression_text)]; - if (!ui_prompt_inline("输入新表达式", "例如:3+4*X2-2*(20-5*2)", + if (!ui_prompt_inline("输入表达式或命令", + "例如:X2^2 或 DEF f(x)=x<=1?1:x*f(x-1); 或 RUN f(5);", input, sizeof(input))) return; struct expression_error error; + const char *text = ui_skip_space(input); + if (strncmp(text, "DEF", 3) == 0 && + !expression_name_char((unsigned char)text[3])) { + if (!expression_define(&state->expression, text, &error)) { + ui_set_status(state, "错误:%s(位置 %zu)", + ui_expression_error_text(error.code), error.position); + return; + } + const struct expression_function *function = + vec_back_const(&state->expression.functions); + ui_set_status(state, "已定义函数 %s(%s),历史记录 %zu", + function->name, function->parameter, state->expression.functions.size); + return; + } + bool run = strncmp(text, "RUN", 3) == 0 && + !expression_name_char((unsigned char)text[3]); + if (run) text = ui_skip_space(text + 3); + size_t length = strlen(text); + while (length > 0 && isspace((unsigned char)text[length - 1])) --length; + if (length > 0 && text[length - 1] == ';') --length; + memmove(input, text, length); + input[length] = '\0'; if (!expression_collect_variables(&state->expression, input, &error)) { ui_set_status(state, "错误:%s(位置 %zu)", ui_expression_error_text(error.code), error.position); @@ -1389,6 +1416,7 @@ static void ui_expression_input(struct ui_state *state) strcpy(state->expression_text, input); state->expression_result_valid = false; ui_set_status(state, "已更新表达式"); + if (run) ui_expression_calculate(state); } static void ui_expression_set_variable(struct ui_state *state) @@ -1429,6 +1457,10 @@ static void ui_expression_calculate(struct ui_state *state) if (!expression_eval(&state->expression, state->expression_text, &state->expression_result, &error)) { state->expression_result_valid = false; + if (error.code == EXPRESSION_UNKNOWN_FUNCTION) { + state->expression_text[0] = '\0'; + vec_clear(&state->expression.variables); + } ui_set_status(state, "错误:%s(位置 %zu)", ui_expression_error_text(error.code), error.position); return; @@ -1437,6 +1469,57 @@ static void ui_expression_calculate(struct ui_state *state) ui_set_status(state, "计算完成"); } +static void ui_expression_history(struct ui_state *state) +{ + if (state->expression.functions.size == 0) { + ui_set_status(state, "尚未定义函数"); + return; + } + ui_clear_status(state); + for (;;) { + tui_clear(); + ui_print_header("函数定义历史"); + for (size_t i = 0; i < state->expression.functions.size; ++i) { + const struct expression_function *function = + vec_at_const(&state->expression.functions, i); + printf("%zu. %s(%s) = %s\n", i + 1, + function->name, function->parameter, function->body); + } + ui_print_status(state); + ui_print_footer("输入记录序号运行历史函数 Esc 返回"); + size_t choice; + if (!ui_read_size_value("运行历史函数", "记录序号:", &choice, state)) return; + if (choice == 0 || choice > state->expression.functions.size) { + ui_set_status(state, "错误:记录序号超出范围"); + continue; + } + const struct expression_function *function = + vec_at_const(&state->expression.functions, choice - 1); + char input[64]; + char description[128]; + snprintf(description, sizeof(description), "输入形参 %s 的值:", function->parameter); + if (!ui_prompt_inline("运行历史函数", description, input, sizeof(input))) return; + char *end; + errno = 0; + double argument = strtod(input, &end); + if (end == input || errno == ERANGE || *ui_skip_space(end) != '\0') { + ui_set_status(state, "错误:数值格式无效"); + continue; + } + double result; + struct expression_error error; + if (!expression_call(&state->expression, choice - 1, argument, &result, &error)) { + ui_set_status(state, "错误:%s(位置 %zu)", + ui_expression_error_text(error.code), error.position); + continue; + } + printf("\n" COLOR_GREEN "历史记录 %zu:%s(%g) = %g" STYLE_RESET "\n", + choice, function->name, argument, result); + tui_wait(); + ui_clear_status(state); + } +} + static void ui_expression_action(struct ui_state *state, int choice) { switch (choice) { @@ -1449,6 +1532,9 @@ static void ui_expression_action(struct ui_state *state, int choice) case 2: ui_expression_calculate(state); break; + case 3: + ui_expression_history(state); + break; default: break; } @@ -1459,7 +1545,7 @@ static void ui_expression_page(struct ui_state *state) size_t selected = 0; for (;;) { ui_draw_expression(state, selected); - int choice = ui_menu_input(&selected, 3); + int choice = ui_menu_input(&selected, 4); if (choice == -1) { return; } |
