Не работает простой лексер на языке Си

я писал простой лексер для элементарного языка но почему-то в цикле while функции получения лексемы и записи ее в список не возвращают значений из-за чего программа уходит в вечный цикл. Текст программы:

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

/* prog.l4

if ?
    := AB & 10:
    := 12 # (:= H) & ABC & 9 ;
:= GG & 5 # F ;
:= 5 & 4 & 3
*/

static FILE *file;

static char buff[128];

typedef enum lexem {
    LEX_EOF = -1,
    LEX_NPRINT,         // ' ' '\n' '\r' '\t'
    LEX_SEMICOLON,      // ;
    LEX_ASSIGN,         // :=
    LEX_IF,             // if
    LEX_QMARK,          // ?
    LEX_COLON,          // :
    LEX_HASH,           // #
    LEX_AMPERSAND,      // &
    LEX_LPAR,           // (
    LEX_RPAR,           // )
    LEX_IDENTIFIER,     // ABBA
    LEX_NUMBER          // 228
} lexem_e;

typedef struct lexem_list {
    lexem_e lexem;
    struct lexem_list *next;
} lexem_list_t;


lexem_e get_next_lexem(FILE *file) {
    char ch = fgetc(file);
    if (isupper(ch)) {
        int i = 0;
        do {
            buff[i++] = ch;
            ch = fgetc(file);
        } while(isupper(ch));
        fseek(file, -1, SEEK_CUR);
        return LEX_IDENTIFIER;
    } else if (isdigit(ch)) {
        int i = 0;
        do {
            buff[i++] = ch;
            ch = fgetc(file);
        } while (isdigit(ch));
        fseek(file, -1, SEEK_CUR);
        return LEX_NUMBER;
    } else if (ch == ':') {
        ch = fgetc(file);
        if (ch == '=') {
            return LEX_ASSIGN;
        }
        fseek(file, -1, SEEK_CUR);
        return LEX_COLON;
    } else if (ch == 'i') {
        ch = fgetc(file);
        if (ch == 'f') {
            return LEX_IF;
        }
        fseek(file, -1, SEEK_CUR);
        printf("E Forbidenn symbol %c\n", ch);
        exit(-1);
    } else if (ch == ';') {
        return LEX_SEMICOLON;
    } else if (ch == '?') {
        return LEX_QMARK;
    } else if (ch == '#') {
        return LEX_HASH;
    } else if (ch == '&') {
        return LEX_AMPERSAND;
    } else if (ch == '(') {
        return LEX_LPAR;
    } else if (ch == ')') {
        return LEX_RPAR;
    } else if (isspace(ch)) {
        return LEX_NPRINT;
    } else if (ch == EOF) {
        return LEX_EOF;
    } else {
        printf("E Forbidden symbol %c\n", ch);
        exit(-1);
    }
}

lexem_list_t *add_lexem_to_list(lexem_list_t *head, lexem_e lexem) {
    if (!head) {
        head = (lexem_list_t *)malloc(sizeof(lexem_list_t *));
        head->next = NULL;
        head->lexem = lexem;

        return head;
    }

    lexem_list_t *tmp = head;
    while (tmp->next != NULL) 
        tmp = tmp->next;
    
    tmp->next = (lexem_list_t *)malloc(sizeof(lexem_list_t *));
    tmp = tmp->next;
    tmp->next = NULL;
    tmp->lexem = lexem;

    return head;
}

void delete_lexem_list(lexem_list_t *head) {
    lexem_list_t *dell;
    while (head != NULL) {
        dell = head;
        head = head->next;
        free(dell);
    }
}

void print_lexems(lexem_list_t *head) {
    while (head->next != NULL) {
        printf("L%d ", head->lexem);
        head = head->next;
    }
}


int main(int argc, char const *argv[]) {
    if (argc > 1) {
        if (strcmp(argv[1] + (strlen(argv[1]) - 3), ".l4") == 0) {
            file = fopen(argv[1], "r");
        } else {
            printf("E Unsupported file type\n");
            return -1;
        }
    }

    if (!file) {
        printf("E Can't open file %s\n", argv[1]);
        return -1;
    }

    lexem_list_t *head = NULL;

    lexem_e lexem;
    while(lexem = get_next_lexem(file) != LEX_EOF) { /* <= Вот тут проблема */
        head = add_lexem_to_list(head, lexem);
    }

    print_lexems(head);

    delete_lexem_list(head);
    fclose(file);
    return 0;
}

Там где стоит метка в коде после первой итерации цикла, переменная lexem почему_то равно LEX_SEMICOLON, а переменная head равна NULL. Пример программы которую прогоняю в комментарии под заголовком prog.l4


Ответы (1 шт):

Автор решения: AndreyKarpov

Рационально при написании лабораторных работ использовать статический анализатор, например, PVS-Studio. Для студентов есть бесплатные лицензии. С помощью анализатора можно быстро найти многие ошибки и двигаться дальше. Понятно, что высокоуровневую ошибку он может не найти, но простые выявит легко. Это сэкономит время. Например, в этом коде он видит сразу несколько проблем.

Запуск анализатора PVS-Studio на этом коде на сайте Compiler Explorer.

Предупреждения:

head = (lexem_list_t *)malloc(sizeof(lexem_list_t *));

Две такие строчки и два предупреждения:

V641 The size of the allocated memory buffer is not a multiple of the element size.

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

} else if (ch == EOF) {

V739 EOF should not be compared with a value of the 'char' type. The 'ch' should be of the 'int' type.

Для хранения результата fgetc надо использовать int, а не char. Иначе буква 'Я' будет восприниматься как EOF. Подробнее это расписано в описании диагностики.

head = (lexem_list_t *)malloc(sizeof(lexem_list_t *));

V522 There might be dereferencing of a potential null pointer 'head'. Check lines: 99, 98.

Четыре причины проверять, что вернула функция malloc.

И место, где анализатор просто обкричался тремя разными предупреждениями:

while(lexem = get_next_lexem(file) != LEX_EOF) { /* <= Вот тут проблема */
  • V593 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'.
  • V768 The expression 'lexem = get_next_lexem(file) != LEX_EOF' is of enum type. It is odd that it is used as an expression of a Boolean-type.
  • V559 Suspicious assignment inside the conditional expression of 'while' statement: lexem = get_next_lexem(file) != LEX_EOF.
→ Ссылка