Удаление cut-line комментариев

Вот программа, которая удаляет различного вида комментарии в текстовом файле. Единственная проблема- она не справляется с некоторыми cut-line комментариями. То есть, удаляет только строку, следующую за первым \ , а дальше выходит из цикла и не считает следующие строки за продолжение комментария

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

Например, при вводных данных

введите сюда описание изображения

Получается

введите сюда описание изображения

Как исправить эту ошибку?

Вот код

#include <stdio.h>

int main() {
    FILE* inputFile, * outputFile;
    char prev_ch;
    char ch;
    inputFile = fopen("input.txt", "r");
    outputFile = fopen("output.txt", "w");
    if (inputFile == NULL && outputFile == NULL) {
        printf("EOF");
        return 1;
    }
    int brackets = 0;
    int comment = 0;
    int multilin = 0;
}



// Перебираем каждый символ в файле
while ((ch = fgetc(inputFile)) != EOF) {
    // 1. Если встречаем кавычку, которая находится ВНЕ КОММЕНТАРИЯ
    if (comment == 0 && ch == '\"') {
        fputc(ch, outputFile);
        while ((ch = fgetc(inputFile)) != '\"' && !feof(inputFile)) {
            fputc(ch, outputFile);
        }
        if (feof(inputFile))
            break;
    }

    // 2. Если встречаем двойной слэш, который находится вне кавычки, начинается однострочный комментарий
    if (brackets == 0 && comment == 0 && ch == '/') {
        prev_ch = ch;
        ch = fgetc(inputFile);
        if (ch == '/')
            comment = 1;
        else if (ch == '*') {
            multilin = 1;
            comment = 1;
            while (((ch = fgetc(inputFile)) != '/' || prev_ch != '*') && !feof(inputFile)) {
                prev_ch = ch;
                multilin = 0;
                comment = 0;
            }
        }
        else if (ch == '\"') {

            fputc(prev_ch, outputFile);
            fputc(ch, outputFile);
            printf("%d ", ch);
            while ((ch = fgetc(inputFile)) != '\"' && !feof(inputFile))
                fputc(ch, outputFile);
            if (feof(inputFile))
                break;
        }
        else {
            fputc(prev_ch, outputFile);
        }
    }

    // 3. При символе \ продолжается комментарий
    if (multilin == 0 && brackets == 0 && comment == 1 && ch == '\\') {
        ch = fgetc(inputFile);
        comment = 1;
        while ((ch = fgetc(inputFile))=='\n');
            ch = fgetc(inputFile);
    }

    // 4. Закрываем однострочный комментарий
    if (brackets == 0 && comment == 1 && multilin == 0 && ch == '\n') {
        comment = 0;
    }


    // 5. Вывод символа, если он не является комментарием или в кавычках
    if (brackets == 1 || multilin == 0 && comment == 0 && ch != '/') {
        fputc(ch, outputFile);
    }
}

fclose(inputFile);
fclose(outputFile);
return 0;

}


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

Автор решения: Stanislav Volodarskiy

Стандартная трудность: начинаем с учёта однострочного комментария, добавляем поддержку многострочных. Код становится в два раза больше и запутывается. Добавляем строковые литералы, код становится ещё в два раза больше и ещё больше запутывается. И уже почти невозможно разобраться в нагромождении условий. Исправление ошибки требует исправить почти весь код и порождает ещё больше ошибок.

Я не могу исправить ваш код. Он сложный: много циклов, вложенные условия. Требуется держать в голове сразу много вещей. Поэтому конечный автомат, один цикл, один символ из входного потока. Это не сделает всю задачу проще, но сделает её более управляемой и, может быть, мы сможем её решить.

Если ткнуть пальцем в код, то мы или в коде или в комментарии. Комментарий начинается с /* или // но не всегда. Если эти символы внутри строки, они не начинают комментарий. В свою очередь строка может начаться с двойной кавычки в коде, но не в комментарии. И есть ещё одно место где не может начаться строка: это символьная константа вроде '"' – двойная кавычка есть, строки нет. Это кажется всё.

То есть, мы имеем

  • CODE – обычный код;
  • CPP_COMMENT – строковый комментарий (стиль C++);
  • C_COMMENT – многострочный комментарий (стиль C);
  • STRING_LITERAL – строковый литерал;
  • CHARACTER_CONSTANT – символьная константа.

Из обычного кода можно перейти в один четырёх других типов. Каждый другой тип возвращается в обычный код.

из текст через текст в
CODE // CPP_COMMENT \n CODE
CODE /* C_COMMENT */ CODE
CODE " STRING_LITERAL " CODE
CODE ' CHARACTER_CONSTANT ' CODE

Не всё так просто: не каждая двойная кавычка завершает строковый литерал. Кавычки могут быть экранированы: \". То же верно для символьных констант. И не всякий перевод строки завершает однострочный комментарий. И между * и / в многострочном комментарии может быть сколько угодно экранированных переводов строк \\n. *\\n\\n/* – корректное завершение многострочного комментария.

Ещё одно усложнение вызвано посимвольной обработкой. Значение * в CODE зависит от предыдущего символа. Если это был /, начался комментарий, иначе это просто умножение или разыменование указателя. Нужны дополнительные состояния в конечном автомате. Можно было бы без них, но с ними проще.

Комментарии в стиле C по стандарту заменяются на один пробел. Печать разбросана по коду, там свои нюансы.

#include <stdio.h>

typedef enum {
    CODE                        , // in plain code
    CODE_SLASH                  , // in plain code after '/'
    CODE_SLASH_BACKSLASH        , // in plain code after '\\' after '/'
    STRING_LITERAL              , // in string literal
    STRING_LITERAL_BACKSLASH    , // in string literal after '\\'
    CHARACTER_CONSTANT          , // in character constant
    CHARACTER_CONSTANT_BACKSLASH, // in character constant after '\\'
    C_COMMENT                   , // in C style comment
    C_COMMENT_ASTERISK          , // in C style comment after '*'
    C_COMMENT_ASTERISK_BACKSLASH, // in C style comment after '\\' after '*' 
    CPP_COMMENT                 , // in C++ style comment
    CPP_COMMENT_BACKSLASH         // in C++ style comment after '\\'
} State;

int main() {
    State state = CODE;
    int linefeeds;
    for (; ; ) {
        int c = fgetc(stdin);
        if (c == EOF) {
            break;
        }

        switch (state) {
        case CODE:
            switch (c) {
            case '/' :
                state = CODE_SLASH;
                linefeeds = 0;
                break;
            case '"' : state = STRING_LITERAL    ; break;
            case '\'': state = CHARACTER_CONSTANT; break;
            }
            if (c != '/') {
                fputc(c, stdout);
            }
            break;
        case CODE_SLASH:
            switch (c) {
            case '\\': state = CODE_SLASH_BACKSLASH; break;
            case '*' : state = C_COMMENT           ; break;
            case '/' : state = CPP_COMMENT         ; break;
            default  : state = CODE                ;
                fputc('/', stdout);
                for (int i = 0; i < linefeeds; ++i) {
                    fputc('\\', stdout);
                    fputc('\n', stdout);
                }
                fputc(c, stdout);
                break;
            }
            break;
        case CODE_SLASH_BACKSLASH:
            if (c == '\n') {
                state = CODE_SLASH;
                ++linefeeds;
            } else {
                fputc('/', stdout);
                for (int i = 0; i < linefeeds; ++i) {
                    fputc('\\', stdout);
                    fputc('\n', stdout);
                }
                fputc(c, stdout);
                state = CODE;
            }
            break;
        case STRING_LITERAL:
            switch (c) {
            case '\\': state = STRING_LITERAL_BACKSLASH; break;
            case '"' : state = CODE                    ; break;
            }
            fputc(c, stdout);
            break;
        case STRING_LITERAL_BACKSLASH:
            state = STRING_LITERAL;
            fputc(c, stdout);
            break;
        case CHARACTER_CONSTANT:
            switch (c) {
            case '\\': state = CHARACTER_CONSTANT_BACKSLASH; break;
            case '\'': state = CODE                        ; break;
            }
            fputc(c, stdout);
            break;
        case CHARACTER_CONSTANT_BACKSLASH:
            state = CHARACTER_CONSTANT;
            fputc(c, stdout);
            break;
        case C_COMMENT:
            if (c == '*') {
                state = C_COMMENT_ASTERISK;
            }
            break;
        case C_COMMENT_ASTERISK:
            switch (c) {
            case '\\':
                state = C_COMMENT_ASTERISK_BACKSLASH;
                break;
            case '/':
                state = CODE;
                fputc(' ', stdout);
                break;
            default:
                state = C_COMMENT;
            }
            break;
        case C_COMMENT_ASTERISK_BACKSLASH:
            state = (c == '\n') ? C_COMMENT_ASTERISK : C_COMMENT;
            break;
        case CPP_COMMENT:
            switch (c) {
            case '\\': state = CPP_COMMENT_BACKSLASH; break;
            case '\n':
                state = CODE;
                fputc('\n', stdout);
                break;
            }
            break;
        case CPP_COMMENT_BACKSLASH:
            state = CPP_COMMENT;
            break;
        }
    }
}

P.S. В настоящем компиляторе этот разбор сделан в несколько этапов, каждый этап проще, всё вместе сложнее.

P.P.S. Позовут писать компилятор, просите много.

→ Ссылка