如果您能够在 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 提供了一些在编译时使用模板元编程连接常量字符串的方法。会有几个复杂的位,例如:将诸如const、volatile 和signed 之类的限定符放入规范顺序中,将指向T(Args...) 的指针表示为T(*)(Args...),以及指向数组的指针的等价物。如果您想通过模板元编程消除所有运行时开销,您可能会定义自己的type_name<T> 模板,使用std::enable_if 和<type_traits> 库分别对其进行专门化,并为连接类型名称的复合类型提供覆盖*,(,),[和]的组件以正确的方式。如果您可以承受少量开销,则可以将它们设为const std::string 并将它们与+ 连接。
您可能希望自下而上执行此操作,从简单类型的名称开始,然后提供用于递归查找构建块的名称。它与函数式语言中的模式保护非常相似。
用 C 语言做这件事要困难得多。
编辑
用 C 语言做这件事并不一定那么难。 (在我们的聊天中,你说你实际上是在 Kotlin 中做的。)
您应该查阅语言标准(或a recent draft)的形式语法,但您感兴趣的语言子集似乎有三种情况:
- 简单类型,通过附加
* 形成一个指针,
- 派生类型,通过在中间添加
(*) 形成一个指针,并且
- 指向派生类型的指针,其中您已经有类似
(**) 的内容,并且您在其中添加了另一个星号。 (永远不要写三星级代码!)
这可以写成一种算法,其中类型具有 left、middle 和 right 部分。对于简单类型,left 是整个类型,另外两个是空的。对于数组,left 是元素类型,right 是边界。对于函数原型,left 是返回类型,right 是参数列表。无论哪种方式,middle 都是空的。对于派生类型的指针,middle 是中间括号内的星号。因此,例如,double(*)[4][4] 的 left 是 double,middle 是 *,right 是 [4][4] .
大多数操作都有两种特殊情况,取决于 middle 是空还是非空。 (这些可以实现为泛型、模式匹配、重载对象方法或案例块的特化。由于我们假设在 C 中执行此操作,因此我们可能拼写为 strlen(middle) ? ... : ...。)
我要从这里开始拼写字符串连接⧺。
如果给定类型有一个空的middle,它在C中的名字是left⧺right,声明是left ⧺ " " ⧺ 名字 ⧺ 对。
如果给定类型有一个非空的middle,它在C中的名字是left ⧺ "(" ⧺ middle ⧺ ") " ⧺ right 和使用它的声明是 left ⧺ "(" ⧺ middle ⧺ name ⧺ ")" ⧺ 对。例如,在声明 int(*callback)(handle) 中,left 是 int,middle 是 *,name 是 callback,并且对是(handle)。
形成一个类型的指针有三种情况:如果有一个非空的中间,则将*附加到中间。如果有一个空的中间和一个非空的right,将middle设置为*。如果 middle 和 right 都为空,则将 * 附加到 left。
如果您想支持 const 和 volatile 指针、K&R 样式的不完整函数类型或 C++ 引用,则有些极端情况需要更多的处理。