LINUX.ORG.RU

Компиляторы C


0

0

Вообщем решил разобраться с тем, каким должен быть нормальный компилятор и попробовать свой написать. Не расчитываю на что-то настоящее, но для начала хотя бы вывод дерева разбора в ASCII.

Собственно мне в GNU компиляторе C и pcc не нравится выделенный отдельно препроцессор(cpp). Стандарт C99 вообще описывает директивы препроцессора как часть языка.

Глянул тут на описание компиляторов Plan 9 from Bell Labs. Собственно в Plan 9 есть отдельный препроцессор, но он компиляторами не используется. Компиляторы используют встроенный препроцессор, совмещенный с лексическим анализом(как и предполагает стандарт). В gcc препроцессор выделен в отдельную стадию до лексического анализа, при этом остается #pragma для обработки компилятором. Если бы не #pragma, то можно было бы разделить стадии, но из-за нее в лексическом анализаторе частично дублируется функциональность препроцессора. Это вообще выглядит как хак, и я больше доверяю Bell Labs, как разработчикам языка C.

Собственно сама статья, которую я читаю: http://cm.bell-labs.com/sys/doc/compiler.html Тут вот описывается, каким образом директивы препроцессора удалось встроить в парсер на yacc:

>5.1. Parsing > >The first pass is a YACC-based parser [Joh79]. Declarations are interpreted immediately, building a block structured symbol table. Executable statements are put into a parse tree and collected, without interpretation. At the end of each procedure, the parse tree for the function is examined by the other passes of the compiler. > >The input stream of the parser is a pushdown list of input activations. The preprocessor expansions of macros and #include are implemented as pushdowns. Thus there is no separate pass for preprocessing.

У меня небольшие проблемы с переводом второго абзаца. pushdown list - это стек или что? Что такое input activations, pushdown и pushdown list?

anonymous

>pushdown list — это стек или что?

(PDL) In ITS days, the preferred MITism for stack. ( http://dictionary.reference.com/browse/Push%20Down%20List ).

>Что такое input activations

«моторные импульсы». в общем, именно то, что ты подумал, скорее всего (гусары, молчать!). некие события в потоке ввода, на которые парзер «возбуждается» и начинает «давить вниз». %-)

mirage
()
Ответ на: комментарий от mirage

>некие события

Такие, как встреча директивы препроцессора?

>«давить вниз»

Это как?

И еще вопрос. Судя по тому, как реализован препроцессор в gcc, не включаемый блок #if может содержать не правильный код(парсинг языка C в препроцессоре не реализован). В то же время по стандарту C99 явно указано, что именно должно быть внутри блока #if. То есть код внутри должен быть правильным, даже если он не включается. Из-за такой вот реализации #if 0 ... #endif стало распространенным способом создания вложенных комментариев. Я где-то ошибся, или же код внутри #if 0 .. #endif должен соотвествовать синтаксису?

anonymous
()
Ответ на: комментарий от anonymous

>Такие, как встреча директивы препроцессора?

угу.

>>«давить вниз»

>Это как?

ну что не ясно-то? если «pushdown list» — это стек, то «давить вниз» (pushdown) — это просто push. немекалось на то, что «засовывать в стек данные». какие — конкретной реализации лучше знать. я вот вообще завсегда hand-written parsers использую, фиг знает, что там и как в тех яках-флексах-бизонах.

>Я где-то ошибся, или же код внутри #if 0 .. #endif должен соотвествовать синтаксису?

а фиг знает. лично я считаю, что верх идиотизма — требовать синтаксической корректности от заведомо некомпилируемого кода. однако стандарты пишут придурки, это аксиома, так что они могли. возможно, это надо тем кретинам, у которых препроцессор жёстко врезан в кишки на высоком уровне (умные существа вставляют его между токенизатором и AST-builder'ом — однако это дублирование кода и потенциальный срач, — самые умные выносят отдельной программой).

как-то так.

mirage
()
Ответ на: комментарий от anonymous

вообще, стек тут применим для реализации вложеных #if. может, у яка какая-то своя специфика, не знаю. да и статью не читал — многабукав, лень. в Драконьей Книге всё давно написано. %-)

mirage
()
Ответ на: комментарий от anonymous

> В то же время по стандарту C99 явно указано, что именно должно
> быть внутри блока #if. То есть код внутри должен быть
> правильным, даже если он не включается.

где ты такое вычитал??  Стадия препроцессора, это отдельная стадия.
Там должны идти корректные препроцессорные токены.  Никакой
корректности кода не требуется.

Например:

#if 0
/* hahaha
#endif

это некорректно потому что комментарии заменяются на пробел до или одновременно с препроцессированием.


> Из-за такой вот реализации #if 0 ... #endif стало
> распространенным способом создания вложенных комментариев.

это стало распространенным потому что если просто закомментарить
код, то это не сработает если в коде есть вложенные комментарии.
А #if 0 можно закомментарить любой корректный код.

> Я где-то ошибся, или же код внутри #if 0 .. #endif
> должен соотвествовать синтаксису?

код внутри должен быть корректным на том уровне который используется
на и до стадии препроцессора.  Корректности на уровне семантики
Си никто не требует.

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

Резюмируя:

Корректно:

#if 0
hahaha
#endif

Некорректно:

#if 0
#hahaha
#endif

Некорректно:

#if 0
"hahaha
#endif

Корректно:

#if 0
zdes zhopa!
#endif

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

Вообщем пробую вручную распарсить код =)

#if 0
blahblah
#endif

Сначала нашел следующее:
if-section:
         if-group elif-groups(opt) else-group(opt) endif-line

elif-groups, else group сразу опускаем(они опциональные):

if-section:
         if-group endif-line

C endif-line все ясно - она всегда выглядит как "#endif\n":
endif-line:
        # endif new-line

Остается:
#if 0
blahblah

И это должно подойти под if-group.

if-group:
         # if     constant-expression new-line group(opt)
         # ifdef  identifier new-line group(opt)
         # ifndef identifier new-line group(opt)

Нам подходит первый вариант:
         # if     constant-expression new-line group(opt)

constant-expression - это "0"

Убираем "#if 0\n". Остается "blahblah", которое должно подойти под group:

group:
        group-part
        group group-part

Смотрим group-part:
group-part:
        if-section
        control-line
        text-line
        # non-directive

Под if-section, control-line и "# non-directive" не подходит - у них у всех решетка первым символом:

control-line:
       # include pp-tokens new-line
       # define identifier replacement-list new-line
       # define identifier lparen identifier-listopt )
                                       replacement-list new-line
       # define identifier lparen ... ) replacement-list new-line
       # define identifier lparen identifier-list , ... )
                                       replacement-list new-line
       # undef   identifier new-line
       # line    pp-tokens new-line
       # error   pp-tokensopt new-line
       # pragma pp-tokensopt new-line
       #         new-line

Видимо, "blahblah" должно подойти под text-line.

text-line:
        pp-tokens(opt) new-line

Т.е. под pp-tokens.

pp-tokens:
       preprocessing-token
       pp-tokens preprocessing-token

Смотрим preprocessing-token:
preprocessing-token:
        header-name
        identifier
        pp-number
        character-constant
        string-literal
        punctuator
        each non-white-space character that cannot be one of the above

Видимо, под последний пункт "blahblah" подходит. Так что код валидный. 

anonymous
()
Ответ на: комментарий от dilmah

Но вообще в стандарте весь синтаксис(как языка так и препроцессора записан вместе - это не различные стадии).

anonymous
()
Ответ на: комментарий от anonymous

как я уже сказал — потому что стандарт делали идиоты.

mirage
()
Ответ на: комментарий от anonymous

> Но вообще в стандарте весь синтаксис(как языка так и препроцессора записан вместе - это не различные стадии).

сомневаюсь.

5.1.1.2 Translation phases

1 The precedence among the syntax rules of translation is specified by the following phases.5) 1. Physical source file multibyte characters are mapped, in an implementationdefined manner, to the source character set (introducing new-line characters for end-of-line indicators) if necessary. Trigraph sequences are replaced by corresponding single-character internal representations. 2. Each instance of a backslash character (\) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. A source file that is not empty shall end in a new-line character, which shall not be immediately preceded by a backslash character before any such splicing takes place. 3. The source file is decomposed into preprocessing tokens6) and sequences of white-space characters (including comments). A source file shall not end in a partial preprocessing token or in a partial comment. Each comment is replaced by one space character. New-line characters are retained. Whether each nonempty sequence of white-space characters other than new-line is retained or replaced by one space character is implementation-defined. 4. Preprocessing directives are executed, macro invocations are expanded, and _Pragma unary operator expressions are executed. If a character sequence that matches the syntax of a universal character name is produced by token concatenation (6.10.3.3), the behavior is undefined. A #include preprocessing directive causes the named header or source file to be processed from phase 1 through phase 4, recursively. All preprocessing directives are then deleted. 5. Each source character set member and escape sequence in character constants and string literals is converted to the corresponding member of the execution character set; if there is no corresponding member, it is converted to an implementationdefined member other than the null (wide) character.7) 6. Adjacent string literal tokens are concatenated. 7. White-space characters separating tokens are no longer significant. Each preprocessing token is converted into a token. The resulting tokens are syntactically and semantically analyzed and translated as a translation unit. 8. All external object and function references are resolved. Library components are linked to satisfy external references to functions and objects not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment.

dilmah ★★★★★
()
Ответ на: комментарий от mirage

>возможно, это надо тем кретинам, у которых препроцессор жёстко врезан в кишки на высоком уровне (умные существа вставляют его между токенизатором и AST-builder'ом — однако это дублирование кода и потенциальный срач, — самые умные выносят отдельной программой).

То есть ты считаешь, что Кен Томпсон - кретин? У него препроцессор врезан в парсер. И это вообще позволяет выполнять весь процесс компиляции(вместе с препроцессингом) за один проход. В случае отделения препроцессора в отдельную стадию - проходов по крайней мере два и код парсится дважды. Из-за этого вообще теряется одно из преимуществ C. Зачем же мы тогда прототипы функций в начале файла пишем, если компиляторы все равно не однопроходные?

>самые умные выносят отдельной программой

А это не дублирование кода по-твоему? #pragma тебе придется парсить как в препроцессоре, так и в самом компиляторе. Можешь попробовать прогнать прагму через cpp - она не изменится и будет оставлена для компилятора. Из-за этого в компиляторе придется частично дублировать парсинг директив препроцессора.

Разве C не задумывался как язык для однопроходной компиляции?

anonymous
()
Ответ на: комментарий от mirage

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

dilmah ★★★★★
()
Ответ на: комментарий от dilmah

Более того, реальный компилятор может вообще без лексера обходиться, один только парсер (и он же и препроцессор).

Только кому это на хрен нужно?

anonymous
()
Ответ на: комментарий от dilmah

зато для Си++ и Си можно фактически реюзать код препроцессинга (по модулю небольших отличий). А если объединять фазы трансляции то будет сложно поддерживаемый дублирующийся код компилятора.

dilmah ★★★★★
()

> Если бы не #pragma, то можно было бы разделить стадии, но из-за нее в лексическом анализаторе частично дублируется функциональность препроцессора. Это вообще выглядит как хак, и я больше доверяю Bell Labs, как разработчикам языка C.

Функциональность дублируется совершенно незначительно. Я бы даже сказал, что вообще не дублируется. Писать новый однопроходный компилятор из-за этого явно не стоит. Более того, при разнесении препроцессора и компилятора достигается определенная модульность системы в целом. Если слить их обратно в кучу, то получим менее структурированную программу. Сам себе усложнишь жизнь.

Burbaka ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.