Алгоритм создания C объявлений типов из AST - PullRequest
1 голос
/ 16 июня 2020

Мне нужно сгенерировать код C из абстрактного синтаксического дерева. Объявление типов сложно. Есть ли где-нибудь записанный алгоритм, как это сделать?

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

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

Абстрактное синтаксическое дерево кодирует C семантика типов, где типы узлов:

  • Basi c типы (int, char et c)

  • Указатель на тип

  • Массив типа (с размером или без него)

  • Функция, принимающая список типов параметров и возвращая тип

Итак, проблема заключается как раз в том, чтобы преобразовать это в синтаксис C.

1 Ответ

1 голос
/ 16 июня 2020

Если вы можете реализовать AST на C ++, STL предоставляет std::type_info::name(), который возвращает имя типа T. В некоторых компиляторах, включая MSV C, это возвращает удобочитаемое имя. На других, включая gcc, он возвращает искаженное имя. G CC предоставляет встроенную функцию abi::__cxa_demangle, чтобы распутать его, а Boost предоставляет более переносимую функцию для этого, boost::core::demangle.

Для достижения полиморфизма времени выполнения вы можете использовать typeid, чтобы получить объект std::type_info во время выполнения, или вы можете унаследовать каждый тип узла AST от абстрактного базового класса. Это может быть чистая виртуальная функция ::name(), реализация которой может быть чем-то вроде

#include <boost/core/demangle.hpp>
#include <typeinfo>

template<typename T>
  const char* AST<T>::name() {
    return boost::core::demangle( typeid(T).name() );
  }

Или что-то еще, использующее кучу блоков #ifdef для поддержки разных компиляторов.

Если хотите Чтобы свернуть свой собственный, эти ответы дают несколько способов конкатенации постоянных строк во время компиляции с использованием метапрограммирования шаблонов. Было бы несколько неудобных битов, таких как: размещение квалификаторов, таких как const, volatile и signed, в каноническом порядке, выражение указателя на T(Args...) как T(*)(Args...) и эквивалент для указателей на массивы. Если вы хотите устранить все накладные расходы времени выполнения с помощью метапрограммирования шаблонов, вы, вероятно, определите свой собственный шаблон type_name<T>, выделите его отдельно, используя std::enable_if и библиотеку <type_traits>, и обеспечите переопределение для составных типов, которые объединяют имена типов компоненты с *, (, ), [ и ] надлежащим образом. Если вы можете жить с небольшими накладными расходами, вы можете просто сделать их const std::string и объединить их с +.

. Вы можете сделать это снизу вверх, начиная с имен для простых типы, а затем предоставление реализаций для array-of-t, указатель-на-array-of-T, et c. которые рекурсивно ищут имена строительных блоков. Это очень похоже на паттерны на функциональном языке.

Выполнить это на C было бы намного, намного сложнее.

Редактировать

Сделать это на C было бы не обязательно быть намного сложнее. (В нашем чате вы говорите, что на самом деле делаете это в Kotlin.)

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

  • Простые типы, где вы формируете указатель, добавляя *,
  • Производные типы, в которых вы формируете указатель, добавив (*) посередине и
  • указатели на производные типы, где у вас уже есть что-то вроде (**), и вы добавляете к нему еще одну звездочку. (На самом деле никогда не пишите трехзвездочный код!)

Это можно записать как алгоритм, в котором типы имеют left , middle и правая часть. Для простых типов left - это весь тип, а два других пустые. Для массивов left - это тип элемента, а right - границы. Для прототипов функций left - это возвращаемый тип, а right - это список аргументов. В любом случае средний пуст. Для указателей на производные типы средний - это звезды внутри средних скобок. Так, например, double(*)[4][4] имеет левый из double, средний из * и правый из [4][4].

Большинство операций имеют два особых случая, в зависимости от того, является ли средний пустым или непустым. (Они могут быть реализованы как специализации обобщений, сопоставления с образцом, перегруженных методов объекта или блока case. Поскольку мы гипотетически делаем это в C, мы, вероятно, написали бы это strlen(middle) ? ... : ....)

С этого момента я буду писать конкатенацию строк ⧺.

Если у данного типа есть пустое среднее , его имя в C будет left справа , а объявление будет left ⧺ "" ⧺ name right .

Если у данного типа есть непустое среднее , его имя в C будет left ⧺ "(" ⧺ middle ⧺ ")" ⧺ справа и объявление с его использованием: left ⧺ "(" ⧺ middle name ⧺ ")" ⧺ right . Например, в объявлении int(*callback)(handle), left is int, middle is *, name is callback и right равно (handle).

Формирование указателей на тип имеет три случая: если есть непустая середина, добавьте * к middle . Если есть пустая середина и непустая правая , установите середину на *. Если оба средний и правый пусты, добавьте * к левому .

Есть угловые случаи, которые требуют немного больше возиться, если вы хотите поддерживать указатели const и volatile, неполные типы функций в стиле K&R или ссылки C ++.

...