Что такое компиляторы и языки программирования

Иллюстрированное введение в работу языков программирования.

Это первая часть из пяти частей, посвященных созданию компиляторов для языков программирования.

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

Я считаю, что это понимание важно для всех разработчиков. Разработчики создают более эффективное программное обеспечение, когда знают, как работают их инструменты: под капотом программ Java или Python всегда происходит больше, чем то, что мы видим на поверхности.

компилятор, программное обеспечение, которое обычно связано с языком программирования, переводит высокоуровневый, удобочитаемый программный код в единицы и нули, известный как машинный код. Это достигается за счет сложного процесса преобразования данных. Он начинается с чтения входного файла, «программы», и заканчивается созданием исполняемого программного обеспечения или другого вывода. транспилятор следует аналогичному процессу, но вместо создания исполняемого программного обеспечения он выбирает создание кода для другого удобочитаемого языка. Диаграмма на рисунке 1 должна помочь вам визуализировать взаимосвязь между кодом и программами, а также компиляторами и транспиляторами.

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

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

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

Лексический анализ

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

Жетоны - это символические единицы, составляющие программу. В Java, например, токен может быть ключевым словом, целочисленной константой, левой круглой скобкой, оператором равенства или строкой. Токены привязаны к их типу и стоимости. Например, маркер левой круглой скобки будет иметь тип Symbol и значение '('. В качестве альтернативы для строки он может иметь тип String и значение «the нить".

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

Семантический анализ

Вы можете представить токены, передаваемые в виде массива. И каждый находится в своем линейном порядке. Цель семантического анализа, более известного как синтаксический анализ, - превратить линейный порядок в дерево операций. То есть вместо каждого токена один за другим вы должны логически подключить каждый токен. Дерево - лучший способ сделать это. У каждого типа подключения есть определенные зависимости, и они могут быть рекурсивными. Например

Я подчеркнул, что семантический анализ ищет закономерности. Важно понимать, что единственный возможный способ построить дерево - это знать, что искать после того, как вы найдете число, знак плюса или открывающую скобку. Парсер очень предсказуем: каждый токен направляет его поиск.

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

Примером создания инструкций вывода типа является способ, которым компилятор Java знает, когда программа говорит 2.5 * 3 умножить удвоить на удвоение, вместо того, чтобы выполнять сложную операцию, умножая два совершенно уникальных типа: удвоение умножения на целое число. Узел преобразования вставляется в дерево после его создания и анализа.

Функциональные языки упрощают выражение деревьев. Если вы знаете функциональный язык, я предлагаю вам изучить разработку компиляторов, используя его, в отличие от императивного языка, такого как C ++ или Java. Дерево может быть синтаксически выражено на функциональном языке, в то время как императивный язык может потребовать использования большого количества указателей или объектов. Такой язык, как C ++, может выражать AST с помощью указателей и выделений, а токены - с помощью классов, структур или даже препроцессора; в то время как такие языки, как Java или C #, будут использовать классы и объекты для представления токенов и узлов в AST. Если вам нужен хороший язык для создания компилятора, выберите наиболее удобный для вас язык.

Генерация кода

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

Ассемблеры похожи на компиляторы для ассемблерного кода. Компиляторы переводят свои высокоуровневые языки в сборку, чтобы ассемблеры скомпилировали в машинный код. Ассемблеры переводят инструкции почти 1: 1 в так называемый перемещаемый машинный код - файлы .o или .obj. Перемещаемый машинный код более известен как перемещаемый. Для каждого исходного файла всегда есть один перемещаемый файл, например каждый .c, .cpp или .asm. Но это означает, что выводом ассемблера является один или несколько неисполняемых файлов. Как нам создать исполняемый файл из этих файлов?

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

Упражнения

  1. Рассмотрим свой любимый язык программирования. Это компилятор или интерпретатор?
  2. В чем преимущества и недостатки компиляторов и интерпретаторов?
  3. Приведите пять примеров токенов, которые вы каждый день пишете в своих программах.
  4. Нарисуйте или представьте дерево синтаксического анализа математической формулы.
  5. Изучите эти две программы, которые были скомпилированы для сборки. У кого меньше сгенерированных инструкций по сборке? Как вы думаете, что быстрее? Https://godbolt.org/z/8DckfW

Я сделал викторину на Google Forms, и вы можете сравнить свои результаты с результатами других читателей для развлечения. Вся полученная информация анонимна.