Подтвердить что ты не робот

Как синтаксический анализатор C различает вызов типа и вызов функции в целом?

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

Мой вопрос заключается в том, как я должен обрабатывать строку следующим образом:

doSomethingWith((foo)(bar));

Может быть, что (foo)(bar) является типом, как в:

typedef int foo;

void doSomethingWith(foo aFoo) { ... }

int main() {
    float bar = 23.6;

    doSomethingWith((foo)(bar));

    return 0;
}

Или может быть, что (foo)(bar) является вызовом функции, как в:

int foo(int bar) { return bar; }

void doSomethingWith(int anInt) { ... }

int main() {
    int bar = 10;

    doSomethingWith((foo)(bar));

    return 0;
}

Мне кажется, что синтаксический анализатор не может определить, к какому из двух случаев он имеет дело только с просмотром строки doSomethingWith((foo)(bar)); Это меня раздражает, потому что я надеялся разделить этап синтаксиса с "интерпретацией" ", где вы фактически определяете, что строка typedef int foo; означает, что foo теперь является допустимым типом. В моем воображаемом сценарии Type a = b + c * d будет разбираться просто отлично, даже если Type, a, b, c и d нигде не определены, и проблемы возникнут позже, когда фактически пытаются" разрешить" идентификаторы.

Итак, мой вопрос: как с этим справляются "реальные" C-парсеры? Является ли разделение между двумя этапами, что я надеялся на наивное желание, или я чего-то не хватает?

4b9b3361

Ответ 1

Исторически, typedefs были относительно поздним дополнением к C. Прежде чем они были добавлены к языку, имена типов состояли из ключевых слов (int, char, double, struct и т.д.) и знаков пунктуации (*, [], ()), и поэтому их легко было однозначно распознать. Идентификатор никогда не может быть именем типа, поэтому идентификатор в круглых скобках, за которым следует выражение, не может быть выраженным выражением.

Typedefs позволил определить идентификатор пользователя как имя типа, что довольно серьезно испортило грамматику.

Взгляните на синтаксис спецификатора типа в стандарте C (я буду использовать версию C90, так как это немного проще):

тип спецификатор:
     недействительным
     char
     короткий
     Int
     длинный
     поплавок
     двойной
     подписан
     без знака
    структура или всесоюзных спецификатор
    перечисление спецификатор
    ЬурейеЕ имя

Все, кроме последнего, могут быть легко распознаны, поскольку они либо являются ключевыми словами, либо начинаются с ключевого слова. Но typedef-name - это всего лишь идентификатор.

Когда компилятор C обрабатывает объявление typedef, он должен, по сути, вводить имя typedef в качестве нового ключевого слова. Это означает, что, в отличие от языка с контекстно-свободной грамматикой, должна быть обратная связь из таблицы символов с парсером.

И даже это немного упрощает. Имя typedef все еще может быть переопределено либо как другое typedef, либо как-то еще во внутренней области:

{
    typedef int foo; /* foo is a typedef name */
    {
        int foo;     /* foo is now an ordinary identifier, an object name */
    }
                     /* And now foo is a typedef name again */
}

Таким образом, имя typedef эффективно является определяемым пользователем ключевым словом, если оно используется в контексте, где имя типа является допустимым, но по-прежнему является обычным идентификатором, если он был обновлен.

TL; DR: Анализ C затруднен.

Ответ 2

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

Ответ 3

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

Интеллектуальные деньги - это создание контекстно-зависимого анализатора и последующее разрешение контекстных зависимостей, изолирование двух задач.

Таким образом, вопрос о том, "как синтаксический анализатор знает тип, переданный из вызова функции", становится нетемой; единственная причина, по которой она существует, заключается в том, что люди настаивают на запутывании сырой парсинга с разрешением имени и типа.

Для более чистой модели рассмотрите использование анализатора GLR. Подробнее см. этот ответ > , используя проблему разрешения того, что

 x*y;

означает в C, ту же проблему для OP, если он еще не опрокинул ее.