Skip to content

Latest commit

 

History

History
24 lines (16 loc) · 5.04 KB

File metadata and controls

24 lines (16 loc) · 5.04 KB

Детали реализации

Работа интерпретатора разбита на 3 ступени:

  • Построение вывода грамматики языка
  • Трансляция вывода грамматики в классы C#
  • Выполнение корневого класса

Вывод грамматики

Для более гибкого описания языка необходимо использовать грамматики, т.к. именно они позволяют работать не с "атомами" (aka символами) а оперировать целыми абстракциями. Когда грамматика в языке не слишком сложная, можно обойтись классическим подходом substring + indexof (привет PlutLog), однако с возрастающей сложностью языка, такой подход стремительно теряет эффективность и вынуждает конструировать костыли пропорционально количеству новых конструкций языка.

Для вывода грамматики был создан свой парсер грамматики основанный на регулярных выражениях и вложенности определений. Отметим что в нашем случае вывод грамматики это дерево состоящее из вершин частей исходника, каждая из которых разбита на части ее определяющие. С его помощью мы с легкостью можем задать что, например, переменная это имя переменной, знак равенства, и выражение (вот как это выглядит на языке нашего парсера более формально variable = [_ p variableName] [_ r '^\s*=\s*'] [entry p expression]).

Таким образом вершина каждой переменной будет иметь два поля - имя переменной (которое равно variableName = [_ r '^(?<name>[0-9a-zA-Z_]+)']) и выражение.

Далее работать с полученным деревом намного удобнее использования сырого текста исходника. Подробнее изучить всю грамматику ORsharp можно в файле FP.ORsharp.Base\Languages\ORsharp.grm, также можно изучить работу своего парсера грамматики.

Трансляция вывода грамматики

Каждая вершина вывода грамматики содержит все описывающие ее аттрибуты. Для трансляции дерева вывода в специализированные классы C# мы можем рекурсивно конструировать каждый аттрибут класса из дерева вывода и присваивать его соответствующему полю класса. Отметим что такое удобство вызвано именно возможностями грамматик.

Выполнение корневого класса

Как и говорилось ранее - корень исходника это последовательность операций. Для получения результата мы получаем ее значение.

Когда мы говорим о выполнении какой либо операции, мы всегда имеем дело с контекстом в котором данная операция выполняется. Контекст в нашем случае это набор доступных нам переменных в области видимости операции (в ORsharp операция видит все переменные которые расположены левее самой операции, а также переменные которые выше (родительские переменные), отметим что в случае родительских переменных их модификация не приведет к изменениям в родительском окружении).

В каждую функцию можно передать аргумент, и функция должна вернуть новую опорную функцию (такая логика унаследована у всех сущностей языка ORsharp). Таким образом каждое выражение будет свернуто в одну функцию, значит, любая пользовательская функция от N аргументов в конечном итоге будет свернута в одну функцию (после передачи ей всех аргументов).