Конструкция компилятора: обработка ссылок на неупорядоченные символы

У меня есть книга драконов, но она, похоже, не касается этой темы …

В большинстве современных языков можно использовать определенные переменные, даже если их появление в коде неупорядочено.

пример

class Foo {
void bar() {
plonk = 42;
}
int plonk;
}

Неважно, что переменная plonk объявляется после функции.

Вопрос
Есть ли лучшая практика / полезный шаблон, как это реализовать? На мой взгляд, есть два подхода:

  1. При разборе добавьте фиктивные символы для невидимых символов. Когда объявление анализируется, эти макеты заменяются их реальными символами. После синтаксического анализа мы можем проверить, остались ли макеты и, если да, вывести ошибку.

  2. Не разбирайте символы при разборе, а только создавайте AST. После разбора шага по AST и в зависимости от узла добавляем символы. Например, узел класса добавляет символы потомков и обрабатывает их после. Например, блоки операторов пересекают дочерние элементы и добавляют символы непосредственно перед обработкой дочернего элемента.

Я ожидаю, что подход 1. проще и полезнее для таких вещей, как «импорт других модулей компиляции».

Редактировать:
Проблема, которую я вижу в подходе 1, заключается в том, что для упорядоченных символов требуется некоторая обработка. Например. без функции невозможно использовать локальный символ перед использованием.

4

Решение

Если вы можете, просто создайте AST и таблицу символов во время разбора. Затем выполните передачу AST, чтобы связать символы с записями таблицы символов. Это по сути ваша стратегия № 2.

Проблема со стратегией № 1, в общем случае, заключается в том, что вы не обязательно знаете, что два экземпляра с одним и тем же именем связаны с одним и тем же символом, пока вы не увидите все объявления. Рассмотрим, например, такой язык, как javascript, в котором доменом привязки для символа является функциональный блок (ошибка IMHO, но вкусы разные), но символы не нужно объявлять перед использованием. В этом случае мы будем рассматривать только те символы, которые называются функциями.

Псевдокод (юридический javascript, как выясняется):

function outer() {
return foo();

function inner() {
return foo();

function foo() {
return "inner's foo";
}
}

function foo() {
return "outer's foo";
}
}

Два использования foo ссылаются на различные символы, что вы не можете знать, пока не достигнете последнего определения foo,

Проблема со стратегией # 2 состоит в том, что не всегда возможно построить AST, не зная кое-что об используемых символах. Например, в C вы не можете разобрать выражение вроде (x)(y) не зная, x это имя типа или что-то, что может быть разыменовано в функцию. (Тоже ошибка, имхо, но кто я?). В C ++ вам также необходимо знать, является ли данный символ шаблоном или нет. Часто это описывается как «вид» символа, в отличие от «типа». В C ++ вам не нужно знать, что такое «тип» x это разобрать (x)(y); вам просто нужно знать, есть ли он или нет. По этой причине C ++ допускает использование определенных символов перед объявлением, но не в том случае, если объявление является typedef,

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

В некоторых языках (например, python) объявления являются необязательными и неявными; в таком случае вы можете присоединить новое определение к текущей области во втором проходе, если символ не найден.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]