Вычислить длину исходного кода функций C

#c #function #c-preprocessor

#c #функция #c-препроцессор

Вопрос:

Я хочу частично автоматизировать оценку кода C (ANSI C99) для университетского курса. Одно свойство, которое я хотел бы вычислить, — это количество строк на функцию C (необязательно исключая пустые строки и строки комментариев).

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

Мне сказали, что регулярное выражение не будет работать. Есть ли умный способ использовать препроцессор gcc?

Комментарии:

1. Вы могли бы посчитать строки между первой { и последней } . Сначала означает начало функции, затем складывайте каждую следующую { и сопоставляйте с ее закрытием } .

2. Спасибо, Пол, но это не сработало бы, если бы у меня были строки или комментарии с фигурными скобками вне функции.

3. Как количество локализаций является хорошей объективной мерой чего-либо? Любую программу на C можно превратить в однострочную.

4. @PSkocik Для программистов начального уровня распространенной проблемой является то, что они пишут слишком длинные функции. Мне нужен автоматизированный инструмент, который поможет мне определить отправленные файлы, на которые я должен обратить особое внимание.

Ответ №1:

В Clang есть переключатель для печати синтаксического дерева.

Например, если я запускаю

 clang -Xclang -ast-dump -fsyntax-only lc.c 
  

на

lc.c:

 int main()
{


}

void f()
{
}
  

Я получаю:

 ...
|-FunctionDecl 0x558d2c812890 <lc.c:1:1, line:5:1> line:1:5 main 'int ()'
| `-CompoundStmt 0x558d2c812970 <line:2:1, line:5:1>
 `-FunctionDecl 0x558d2c8129c8 <line:7:1, line:9:1> line:7:6 f 'void ()'
   `-CompoundStmt 0x558d2c812a68 <line:8:1, line:9:1>
  

Если вы напишете скрипт, который извлекает номера строк из тех значений depth= 1 CompoundStmt , которые предшествуют FunctionDecl (FunctionDecl CompoundStmt == определение функции) и вычтет их, вы получите длину строк ваших функций минус 1.

Препроцессор — это немного больше, чем токенизатор. Для этого вам нужен правильный анализатор.

Ответ №2:

Вы можете решить эту проблему в 2 шага:

  • напишите анализатор C, который может удалять комментарии
  • используйте этот анализатор для определения имен и тел функций и подсчета значимых строк кода. Вы должны считать пустые строки и строки, состоящие из фигурных скобок и знаков препинания, бессмысленными ( { , { , , , ; …). Это сделает ваш подсчет менее зависимым от стиля кодирования, используемого программистом.

Вот справка для первого шага: анализатор, который удаляет комментарии:

 /* strip C comments by chqrlie */

#include <errno.h>
#include <stdio.h>
#include <string.h>

/* read the next byte from the C source file, handing escaped newlines */
int getcpp(FILE *fp, int *lineno_p) {
    int ch;
    while ((ch = getc(fp)) == '\') {
        if ((ch = getc(fp)) != 'n') {
            ungetc(ch, fp);
            return '\';
        }
        *lineno_p  = 1;
    }
    if (ch == 'n')
        *lineno_p  = 1;
    return ch;
}

int main(int argc, char *argv[]) {
    FILE *fp = stdin, *ft = stdout;
    const char *filename = "<stdin>";
    int ch, lineno;

    if (argc > 1) {
        if ((fp = fopen(filename = argv[1], "r")) == NULL) {
            fprintf(stderr, "Cannot open input file %s: %sn",
                    filename, strerror(errno));
            return 1;
        }
    }
    if (argc > 2) {
        if ((ft = fopen(argv[2], "w")) == NULL) {
            fprintf(stderr, "Cannot open output file %s: %sn",
                    argv[2], strerror(errno));
            return 1;
        }
    }
    lineno = 1;
    while ((ch = getcpp(fp, amp;lineno)) != EOF) {
        int startline = lineno;
        if (ch == '/') {
            if ((ch = getcpp(fp, amp;lineno)) == '/') {
                /* single-line comment */
                while ((ch = getcpp(fp, amp;lineno)) != EOF amp;amp; ch != 'n')
                    continue;
                if (ch == EOF) {
                    fprintf(stderr, "%s:%d: unterminated single line commentn",
                            filename, startline);
                    break;
                }
                putc('n', ft);  /* replace comment with newline */
                continue;
            }
            if (ch == '*') {
                /* multi-line comment */
                int lastc = 0;
                while ((ch = getcpp(fp, amp;lineno)) != EOF) {
                    if (ch == '/' amp;amp; lastc == '*') {
                        break;
                    }
                    lastc = ch;
                }
                if (ch == EOF) {
                    fprintf(stderr, "%s:%d: unterminated commentn",
                            filename, startline);
                    break;
                }
                putc(' ', ft);  /* replace comment with single space */
                continue;
            }
            putc('/', ft);
            /* keep parsing to handle n/"a//"[i] */
        }
        if (ch == ''' || ch == '"') {
            int sep = ch;
            const char *const_type = (ch == '"') ? "string" : "character";

            putc(sep, ft);
            while ((ch = getcpp(fp, amp;lineno)) != EOF) {
                putc(ch, ft);
                if (ch == sep)
                    break;;
                if (ch == '\') {
                    if ((ch = getcpp(fp, amp;lineno)) == EOF)
                        break;
                    putc(ch, ft);
                }
                if (ch == 'n') {
                    fprintf(stderr, "%s:%d: unescaped newline in %s constantn",
                            filename, lineno - 1, const_type);
                    /* This is a syntax error but keep going as if constant was terminated */
                    break;
                }
            }
            if (ch == EOF) {
                fprintf(stderr, "%s:%d: unterminated %s constantn",
                        filename, startline, const_type);
                break;
            }
            continue;
        }
        putc(ch, ft);
    }
    if (fp != stdin)
        fclose(fp);
    if (ft != stdout)
        fclose(ft);
    return 0;
}