【问题标题】:Algorithm to produce C type declarations from AST从 AST 生成 C 类型声明的算法
【发布时间】:2020-10-05 15:26:29
【问题描述】:

我需要从抽象语法树生成 C 代码。类型声明很棘手。有没有写下来的算法来做到这一点?

这里有几个关于逆向发展的问题。我不是要解析 C 声明,而是要生成它们。

我已尝试检查 cdecl 的代码,这是我所知道的唯一一个相当短的程序,但当我删除所有代码时,显然 不是 用于生成 C 声明的代码,我最终得到一个空文件,所以我显然遗漏了一些东西。

抽象语法树编码 C 类型语义,其中节点的种类是:

  • 基本类型(intchar 等)

  • 指向类型的指针

  • 一个类型的数组(有或没有大小)

  • 获取参数类型列表并返回类型的函数

所以问题只是将其转换为 C 语法。

【问题讨论】:

  • “抽象语法树”非常不具体。如果 AST 对 C 类型语法进行建模,那么它应该是微不足道的。如果不是,那么如果不了解更多关于 AST 的信息,就无法回答这个问题。
  • @rici 它对 C 类型语义进行建模,例如“指向 X 的指针”、“X 数组”、“获取此参数类型列表并返回此类型的函数”有多种节点。困难的部分是弄清楚如何生成 C 语法。
  • 如果它对 c 类型语义进行建模,那么您的 AST 必须包含指针和数组等语言特性。如果没有您的 AST 详细信息,您根本无法做出回应。
  • @barny 好的,添加了 AST 的详细信息。

标签: c types code-generation


【解决方案1】:

如果您能够在 C++ 中实现 AST,则 STL 提供 std::type_info::name(),它返回类型名称 T。在某些编译器(包括 MSVC)上,这会返回人类可读的名称。在其他情况下,包括gcc,它会返回一个错位的名称。 GCC 提供了一个 abi::__cxa_demangle 内置函数来解开它,Boost provides a more portable function 用于此,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 块来支持不同的编译器。

如果你想自己动手,these answers 提供了一些在编译时使用模板元编程连接常量字符串的方法。会有几个复杂的位,例如:将诸如constvolatilesigned 之类的限定符放入规范顺序中,将指向T(Args...) 的指针表示为T(*)(Args...),以及指向数组的指针的等价物。如果您想通过模板元编程消除所有运行时开销,您可能会定义自己的type_name&lt;T&gt; 模板,使用std::enable_if&lt;type_traits&gt; 库分别对其进行专门化,并为连接类型名称的复合类型提供覆盖*()[]的组件以正确的方式。如果您可以承受少量开销,则可以将它们设为const std::string 并将它们与+ 连接。

您可能希望自下而上执行此操作,从简单类型的名称开始,然后提供用于递归查找构建块的名称。它与函数式语言中的模式保护非常相似。

用 C 语言做这件事要困难得多。

编辑

用 C 语言做这件事并不一定那么难。 (在我们的聊天中,你说你实际上是在 Kotlin 中做的。)

您应该查阅语言标准(或a recent draft)的形式语法,但您感兴趣的语言子集似乎有三种情况:

  • 简单类型,通过附加* 形成一个指针,
  • 派生类型,通过在中间添加(*) 形成一个指针,并且
  • 指向派生类型的指针,其中您已经有类似(**) 的内容,并且您在其中添加了另一个星号。 (永远不要写三星级代码!)

这可以写成一种算法,其中类型具有 leftmiddleright 部分。对于简单类型,left 是整个类型,另外两个是空的。对于数组,left 是元素类型,right 是边界。对于函数原型,left 是返回类型,right 是参数列表。无论哪种方式,middle 都是空的。对于派生类型的指针,middle 是中间括号内的星号。因此,例如,double(*)[4][4]leftdoublemiddle*right[4][4] .

大多数操作都有两种特殊情况,取决于 middle 是空还是非空。 (这些可以实现为泛型、模式匹配、重载对象方法或案例块的特化。由于我们假设在 C 中执行此操作,因此我们可能拼写为 strlen(middle) ? ... : ...。)

我要从这里开始拼写字符串连接⧺。

如果给定类型有一个空的middle,它在C中的名字是leftright,声明是left ⧺ " " ⧺ 名字

如果给定类型有一个非空的middle,它在C中的名字是left ⧺ "(" ⧺ middle ⧺ ") " ⧺ right 和使用它的声明是 left ⧺ "(" ⧺ middlename ⧺ ")" ⧺ 。例如,在声明 int(*callback)(handle) 中,leftintmiddle*namecallback,并且(handle)

形成一个类型的指针有三种情况:如果有一个非空的中间,则将*附加到中间。如果有一个空的中间和一个非空的right,将middle设置为*。如果 middleright 都为空,则将 * 附加到 left

如果您想支持 constvolatile 指针、K&R 样式的不完整函数类型或 C++ 引用,则有些极端情况需要更多的处理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 2012-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-15
    相关资源
    最近更新 更多