如何在 C++ 中为动态类型编程语言创建类似“PyObject”的结构?

问题描述 投票:0回答:1

我正在开发一种 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;
c++ types dynamic c++20 programming-languages
1个回答
0
投票

我会回答我自己的问题,因为我已经有点弄清楚了。还添加了我个人项目中的类结构的一些代码片段。 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)。

一些要研究细节的主题:

  • 类型擦除
  • 外部函数接口 (FFI)
  • DLL 符号查找(C 互操作)

这并不是最终的答案,仍在学习中。希望能给其他人一些提示!

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.