我正在开发一种 C++ 编程语言。我创建了一个解析器,它为我的语言创建了完整的抽象语法树。我的想法的下一步是将 AST 转译回 C++ 代码。 所以它是一种编译型编程语言,但你可以直接从 C++ 调用它。该语言的目标是与 C++ 具有互操作性的简单脚本语言。
问题
当我想让脚本语言成为动态类型语言时,困难就出现了。我读到了有关 void* 类型的内容,它可以指向任何导致我创建
AnyTypePointer
对象的内容。
最终意识到这与
std:any
相同,因为无论如何我都必须在检索时检查每种类型。如果不将包含的指针实际转换为它可能的类型并且失败然后重试,则无法检查包含的类型。
所以我的另一个尝试是使用元组和
std::visit
,它允许我迭代任何类型,但问题是元组是模板而不是类变量一旦分配就无法更改类型。
std::variant
尝试也失败了,因为值的类型一旦分配就可能不会更改。
我盯着屏幕,不知道该往哪里走——也许这是一条死胡同。
我还可以尝试哪些其他选择?我尝试了自定义
raw void* class
、std::any
+ type_index map
、std::variant
、std::tuple
+ std::visit
。似乎没有什么可以完成这项工作。
我读过有关用 C 编写的 PyObject 的内容,它执行这个确切的任务。但我似乎无法将其翻译为 C++,因为我对 C 的理解很低。
如何在 C++ 中为我的动态类型语言创建类似 PyObject 的类或系统?方向正确的一点会有所帮助。我找不到任何与 C++ 相关的信息,只有 C。
这是我的编程语言的示例脚本:
example2.candi
// These classes are unrelated in hierarchy.
// But We can still make a function that works with all of them.
#class Horse {
#func make_sound() { #return "Neigh!"};
}
#class Cow {
#func make_sound() { #return "Moo!"};
}
#class Wolf {
#func make_sound() { #return "Oooo!"};
}
#class Cricket {
#func make_sound() { #return "Chirp!"};
}
#func make_sound(animal) {
#return animal.make_sound();
}
#var farm_animals = {Horse(), Cow()};
#var all_animals = farm_animals + {Wolf(),Cricket()}; // You can combine the generic list.
#func make_sounds(animal_list{}) {
#var sounds &string;
#for(animal : animal_list) {
sounds += make_sound(animal);
}
#return sounds;
}
#return make_sounds(all_animals);
这是示例转译代码,运行良好,但不可扩展:
// Typedefs for ease of reading on stackoverflow, transpilation would use the actual types/method calls.
using any_list = std::vector<std::any>;
any_list concat_vector(const any_list& a, const any_list& b) {
any_list result;
result.reserve(a.size() + b.size());
result.insert(result.end(), a.begin(), a.end());
result.insert(result.end(), b.begin(), b.end());
return result;
}
any_list operator+(const any_list& a, const any_list& b) {
return concat_vector(a, b);
}
struct script_example2 {
auto run() {
class Horse {
public:
auto make_sound() {
return "Neigh!";
}
};
class Cow {
public:
auto make_sound() {
return "Moo!";
}
};
class Wolf {
public:
auto make_sound() {
return "Oooo!";
}
};
class Cricket {
public:
auto make_sound() {
return "Chirp!";
}
};
auto make_sound = [](auto& animal) {
return animal.make_sound();
};
std::any farm_animals = any_list{ Horse(), Cow() }; // WE KNOW farm_animals is of type std::vector<std::any> holding Horse, Cow types
std::any all_animals = std::any_cast<any_list>(farm_animals) + any_list{Wolf(),Cricket()}; // WE KNOW all_animals contains Wolf and Crickets too now.
auto make_sounds = [](auto& animal_list) {
// How can we know its a list ? We can't. We can only know its a std::any. or test for all possible types.
std::string sounds;
for (auto& animal : std::any_cast<any_list>(animal_list)) {
// Hmm.. apparently we cant find out what type is contained in the animal list? How can the contained type be stored along with the list?
// We can test for every possible type, but that is not very efficient.
// Using a visitor parent still requires a check for every possible type.
// How does Python do it?!
try {
sounds += std::any_cast<Horse>(animal).make_sound();
}
catch (const std::bad_any_cast& e) {
try {
sounds += std::any_cast<Cow>(animal).make_sound();
}
catch (const std::bad_any_cast& e) {
try {
sounds += std::any_cast<Wolf>(animal).make_sound();
}
catch (const std::bad_any_cast& e) {
try {
sounds += std::any_cast<Cricket>(animal).make_sound();
}
catch (const std::bad_any_cast& e) {
throw std::runtime_error("Type cannot make_sound");
}
}
}
}
}
return sounds;
};
return make_sounds(all_animals);
}
在C++中使用脚本:
candi::script_example1 script;
auto result = script.run();
std::cout << "Script result: " << result << std::endl;
我会回答我自己的问题,因为我已经有点弄清楚了。还添加了我个人项目中的类结构的一些代码片段。 sl_ 与 std:: 相同,rtenv 是运行时环境类。实际上,困难在于找到这个概念的名称,以便您可以研究它。
类 PyObject 的类是指向文字类型的指针或包含指向文字类型的指针的其他 PyObject 的组合。 最后,所有结构都是文字成员和方法的组合:
struct runtime_value {
enum eType {
NUMBER = 0,
REAL = 1,
STRING = 2,
BIT = 3,
BYTE = 4,
NONE = 5,
UNSIGNED = 6,
OBJECT = 7,
FUNCTION = 8
} type;
sl_variant<int, double, sl_string, bool, unsigned char, none_t,unsigned, sl_shared_ptr<runtime_object>,sl_shared_ptr<function_t>> value;
}
class runtime_object {
sl_string name_;
rtenv& scope_;
sl_map<sl_string, runtime_value > members_;
sl_map<sl_string, runtime_method > methods_;
}
下一个谜团是如何在 PyObject 中存储方法,方法是如何建模的?
您只需存储方法定义中未评估的 AST。然后,当您调用该方法时,您可以根据当前环境变量(局部变量/全局变量)评估 AST。
class runtime_method {
sl_string name_;
rtenv& scope_;
sl_vector<sl_string> args_;
astnode body_;}
最后,我很乐意被证明是错误的,但我相信如果您是解释型动态类型语言,则不可能实现与 C++ 的无缝互操作性。具有无缝互操作性的语言也是静态的(D、Go)。
一些要研究细节的主题:
这并不是最终的答案,仍在学习中。希望能给其他人一些提示!