Как мне написать программу на awk, которая соответствует #ifdef ... # else ... # endif C макросам препроцессора? - PullRequest
1 голос
/ 10 апреля 2019

У меня большой корпус программ на C, в которых есть следующие блоки кода.

100. #ifdef DEBUG1
    .
    .
    .
102. #else    
    .
    .
    .
105. #endif

или

200. #ifdef DEBUG2
    .
    .
    .
206. #endif

Кроме того, один файл может содержать несколько макросов #DEBUG. Я хочу извлечь номера строк, соответствующие макросам. Предполагая, что числа в фрагменте кода являются номерами строк в исходном файле, я хочу, чтобы выходные данные имели следующий формат:

FILE - MACRO_NAME - IFDEF - ELIF - ENDIF
----------------------------------------
prog.c - DEBUG1   -  100  -  102  -  105
prog.c - DEBUG2   -  200  -   X   -  206

Как я могу написать awk программу для достижения того же? Если awk не подходит, какой инструмент я могу использовать?

Ответы [ 3 ]

2 голосов
/ 10 апреля 2019

Awk на самом деле имеет ассоциативных массивов, поэтому я бы выбрал следующий подход:

  1. Для каждого #ifdef (или эквивалент, такой как #if 1),увеличить переменную, затем использовать ее для хранения номера строки if, задав для номеров строки else и endif значение -1. ​​

  2. Для строки #else используйтетекущая переменная для установки номера строки else.

  3. Для #endif выведите все данные, которые у вас есть для номеров строк, затем уменьшите значение переменной.

  4. Для #elif необходимо объединить действия #else и #if и убедиться, что соответствующие #end закрывают все строки #if/#elif.

Например, вот автономный скрипт bash, показывающий, как он может работать:

#!/usr/bin/env bash

# Use this script as input file as well, luckily C preprocessor
# macros look like bash comments.

#ifdef XYZZY
    # Some text inside the first ifdef
    #if 0
        # This is the inner bit.
    #endif
    #if 1
        # blah blah blah
    #elif defined TWISTY
        # yada yada yada
    #elif defined PASSAGES
        # run out of phrases
    #else
        # still got nothing
    #endif
#else
    #ifdef PLUGH
        # This is the plugh stuff
    #else
        # This is the anti-plugh stuff
    #endif
#endif

awk <$0 '
    $1 == "#ifdef" || $1 == "#if" {
        level++
        line_mac[level] = $0
        gsub(/^[ \t]+/, "", line_mac[level])
        line_if[level] = NR
        line_else[level] = "X"
        line_end[level] = "X"
        typ_elif[level] = 0
        next
    }
    $1 == "#elif" {
        line_else[level] = NR
        level++
        line_mac[level] = $0
        gsub(/^[ \t]+/, "", line_mac[level])
        line_if[level] = NR
        line_else[level] = "X"
        line_end[level] = "X"
        typ_elif[level] = 1
        next
    }
    $1 == "#else" {
        line_else[level] = NR
        next
    }
    $1 == "#endif" {
        while (typ_elif[level] == 1) {
            printf "if-line %-4s, else-line %-4s, endif-line %-4s, macro '%s'\n", line_if[level], line_else[level], NR, line_mac[level]
            level--
        }
        printf "if-line %-4s, else-line %-4s, endif-line %-4s, macro '%s'\n", line_if[level], line_else[level], NR, line_mac[level]
        level--
    }
    '

Вывод этого (с пронумерованными строками из файла для проверки)это:

 1: #!/usr/bin/env bash
 2: 
 3: # Use this script as input file as well, luckily C preprocessor
 4: # macros look like bash comments.
 5: 
 6: #ifdef XYZZY
 7:     # Some text inside the first ifdef
 8:     #if 0
 9:         # This is the inner bit.
10:     #endif
11:     #if 1
12:         # blah blah blah
13:     #elif defined TWISTY
14:         # yada yada yada
15:     #elif defined PASSAGES
16:         # run out of phrases
17:     #else
18:         # still got nothing
19:     #endif
20: #else
21:     #ifdef PLUGH
22:         # This is the plugh stuff
23:     #else
24:         # This is the anti-plugh stuff
25:     #endif
26: #endif

if-line 8   , else-line X   , endif-line 10  , macro #if 0
if-line 15  , else-line 17  , endif-line 19  , macro #elif defined PASSAGES
if-line 13  , else-line 15  , endif-line 19  , macro #elif defined TWISTY
if-line 11  , else-line 13  , endif-line 19  , macro #if 1
if-line 21  , else-line 23  , endif-line 25  , macro #ifdef PLUGH
if-line 6   , else-line 20  , endif-line 26  , macro #ifdef XYZZY
1 голос
/ 10 апреля 2019

Учитывая ваш комментарий что-то вроде этого должно быть все, что вам нужно (используя GNU awk для ENDFILE и ARGIND):

awk '
{ hit = 0 }
$1 == "#ifdef" {
    macroname = $2
    count[macroname]++
    hit = 1
}
$1 ~ /#(else|endif)$/ { hit = 1 }
hit { fnr[macroname,count[macroname],$1] = FNR; hit = 0 }
ENDFILE {
    if (ARGIND == 1) {
        print "FILE", "MACRO_NAME", "IFDEF", "ELIF", "ENDIF"
    }
    for (macroname in count) {
        for (i=1; i<=count[macroname]; i++) {
            print FILENAME, macroname, fnr[macroname,i,"#ifdef"]+0, fnr[macroname,i,"#elif"]+0, fnr[macroname,i,"#endif"]+0
        }
    }
    delete count
    delete fnr
}
' *.c

Конечно, это не проверено, поскольку вы не предоставили пример, с которым мы могли бы протестировать. Установите OFS или используйте printf или трубу на column, чтобы получить форматированный вывод, если вам все равно.

1 голос
/ 10 апреля 2019

Чтобы расширить ответ @ paxdiablo.Если у вас gawk и вы используете несколько файлов в качестве входных данных, вы можете воспользоваться правилами BEGINFILE и ENDFILE для печати макросов из каждого файла.

Примечание, в отличие от блоков BEGIN/END, которые запускаются только один раз в начале и конце всех входных данных, они запускаются в начале / конце каждого файла (не задумываясь).

Итак, упрощенный скрипт, игнорирующий #else и т. д., для которогоВы можете просто добавить дополнительные правила, скрипт awk, подобный следующему, может быть полезен для нескольких входных файлов,

#!/usr/bin/awk -f

BEGIN {
    printf "%-10s | %-10s | %-5s | %-5s\n", "FILE", "MACRO", "IFDEF", "ENDIF";
    print "----------------------------------------"
}

BEGINFILE {
    delete macros;
    delete locs;
    i = 0;
}

/^[ \t]*#ifdef[\t ]+([^ \t])+/ {
    macros[i++] = $2;
    locs[i]["start"] = FNR;
}

/^[ \t]*#endif/ {
    locs[--i]["end"] = FNR;
}

ENDFILE {
    for (i = 0; i < length(macros); i++) {
        printf "%-10s - %-10s - %-4d - %-4d\n", 
            FILENAME, macros[i], locs[i]["start"], locs[i]["end"];
    }
}

, который должен вывести что-то вроде следующего,

$ ./defs.awk tst.h tst2.h 

FILE       | MACRO      | IFDEF | ENDIF
----------------------------------------
tst.h      - DEBUG1     - 0    - 5   
tst.h      - INNER1     - 1    - 4   
tst2.h     - DEBUG2     - 0    - 3   
...