Обозначить арифметическое выражение в C

Эта программа токенизирует арифметическое выражение, указанное в инфиксной нотации. Это один из моих личных проектов, и я хотел бы получить совет специалиста.

После компиляции вы можете запустить программу с одним аргументом командной строки:

$ tokenize <arith_expr>

Это пример запуска программы:

$ tokenize "3^2 + 4.5 * (-2 - 1)"

Токенизатор в настоящее время поддерживает эти операторы. + - * / ^.

#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Can store an operator or an operand */
struct token {
    char operator;
    float operand;
};

/* Check if given token is an operator */
bool is_operator(char token)
{
    if (token == '+' ||
        token == '-' ||
        token == '*' ||
        token == "https://codereview.stackexchange.com/" ||
        token == '^')
        return true;
    else
        return false;
}

/* Remove all spaces from string */
void rmspaces(char *str)
{
    const char *dup = str;
    do {
        while (isspace(*dup))
            ++dup;
    } while (*str++ = *dup++);
}

/* Return first token of given arithmetic expression */
struct token *tokenize(char **expr)
{
    static bool op_allowed = false;
    struct token *token = malloc(sizeof *token);
    if (!token) {
        printf("Error: memory allocation failed");
        exit(EXIT_FAILURE);
    }

    if (op_allowed && is_operator(*expr[0])) {
        token->operator = *expr[0];
        ++(*expr);
        op_allowed = false;
    } else if (!op_allowed && *expr[0] == '(') {
        token->operator = *expr[0];
        ++(*expr);
    } else if (op_allowed && *expr[0] == ')') {
        token->operator = *expr[0];
        ++(*expr);
    } else {
        char *rest;
        token->operand = strtof(*expr, &rest);
        token->operator="";
        if (*expr == rest) {
            printf("Invalid expressionn");
            exit(EXIT_FAILURE);
        }
        strcpy(*expr, rest);
        op_allowed = true;
    }

    return token;
}

/* Handle command line arguments */
int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("Usage:   %s <arith_expr>n"
            "Example: %s "5 2 3 * +"n",
            argv[0], argv[0]);
        exit(EXIT_FAILURE);
    }

    char *expr = argv[1];
    rmspaces(expr);
    struct token *token;
    while (expr[0] != '') {
        token = tokenize(&expr);
        if (token->operator == '') // token is operand
            printf("%fn", token->operand);
        else
            printf("%cn", token->operator);
    }

    return EXIT_SUCCESS;
}

1 ответ
1

Ваш is_operator функцию можно переписать как

return token == '+' || token == '-' || token == '*' || token == "https://codereview.stackexchange.com/" || token == '^';

На мой взгляд, намного чище.


В программе утечка памяти. Ваш tokenize функция выделяет struct token в куче, но объект никогда не освобождается.

Еще лучше, вместо того, чтобы возвращать struct token*, просто верни struct token. Структура определенно недостаточно велика, чтобы гарантировать выделение памяти в куче.


Вместо того, чтобы проверять, token является операндом путем сравнения operator к 0используйте перечисление.

typedef enum { 
       TokenType_Operator, 
       TokenType_Operand 
} token_type;

Тогда твой token структура может состоять из token_type перечисление и объединение.

struct token
{
   token_type type;
   union {
       char operator;
       float operand;
   } data;
};

Если вы используете C11, вам даже не нужно ничего называть объединению.


В качестве следующего шага попробуйте реализовать токенизатор, который выполняет токенизацию за один проход. Прямо сейчас ваш токенизатор 1) удаляет пробелы (что включает линейный проход) и 2) копии остальной части строки после токенизации операнда в исходный буфер. Если у вас большой буфер, это может стать узким местом.

Распространенный способ реализации токенизатора — использование DFA (детерминированных конечных автоматов). Фактически, DFA — это отправная точка для большинства рукописных лексеров.

  • Токенизатор является частью запрограммированного мною калькулятора, и упомянутые вами баллы очень полезны, спасибо!

    — Энди Суковски-Банг


Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *