Разработка компилятора. Состав языка программирования


Notice: Функция get_currentuserinfo с версии 4.5.0 считается устаревшей! Используйте wp_get_current_user(). in /hlds/web/u138079p19/code4life.ru/htdocs/wp-includes/functions.php on line 3840

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

С некоторой долей уверенности ответ я могу дать только сейчас, когда мы со дня на день выпустим первую публичную версию нашего скриптового языка программирования s4g.

Летом 2016 года, будучи без связи с внешним миром, я задался вопросом, а из чего же состоит язык программирования?

А все очень просто.

Есть метод прямой интерпретации, но ИМХО он подходит только для простейших языков, синтаксис которых ограничен командой и аргументом (аргументами), этот язык больше  похож на байт код для виртуальной машины:

push 10
push 5
add

Но такой метод не интересует, а интересно в плане высокоуровневых языков.

Если коротко, то состав языка программирования (скриптового) таков:

  • лексического анализатора
  • синтаксического анализатора
  • оптимизатора
  • компилятора
  • виртуальной машины
  • сборщика мусора

Так как это вводный экскурс, то пройдемся кратко по каждому элементу.

Лексический анализатор (лексер — lexer)

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

Лексический анализ — процесс распознавания исходного кода и деление его на лексемы.

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

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

a = 10

лексемы:

  • тип: идентификатор символы: «a»
  • тип: знак присваивания символы: «=»
  • тип: целое число символы: «10»

В нашей версии перед этапом лексического анализа следует еще и препроцессор, на подобии C++ препроцессора.

Более подробно в этой статье.

Синтаксический анализатор (парсер — parser)

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

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

То есть это дерево уже больше подходит для компиляции по нему кода для исполнения.

Оптимизатор (optimizer)

объект производящий оптимизации абстрактного синтаксического дерева, сокращая ее вершины, путем оптимизации операций, либо удаляя из-за отсутствия/неиспользования данных.

Простой пример:

a = 10 + 5;

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

Компилятор (compiler)

объект производящий преобразование абстрактного синтаксического дерева в последовательность байт-кода, для определенной виртуальной машины.

Пример (без учета оптимизаций):

10 + 5

На этапе компиляции будет преобразован в что-то подобное:

push 10
push 5
add

Виртуальная машина (virtual machine, VM)

объект исполняющий байт-код, который является аппаратно-независимым, и обычно применим только к какой-то одной виртуальной машине.

Машина внутри управляет всем исполнением, контролирует все что может, она по сути является некой абстрагированной и отдаленной моделью процессора.

Сборщик мусора (garbage collector, GC)

объект выполняющий все операции с памятью — выделение/освобождение. Сборщик мусора нужен для того чтобы разгрузить скриптера от необходимости контролировать выделение и удаление памяти. Это имеет определенные преимущества.


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

//! создать переменную со значением null (то есть пустую переменную)
s4g_Variable* GCcreateVarNull(s4g_Main *s4gm, int typeVar = S4G_GC_TYPE_VAR_FREE);

//! создать переменную со значением bf
s4g_Variable* GCcreateVarBool(s4g_Main *s4gm, s4g_bool bf, int typeVar = S4G_GC_TYPE_VAR_FREE);

s4g_Main — структура скриптовой системы, а s4gm объект скриптовой системы. Как видно все просто))

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

Немаловажной частью является доступное API для создания и управления скриптовой системой.

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

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

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

Поделиться:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*