Parsing 制作玩具语言解释器、AST变量和作用域 标题中,我最近开始和C++的一个C++解析器/解释器合作,稍后将被集成在一个更大的项目中(如果它工作);我们决定开始制作用于抽象语法树的类,稍后我们将在适当的解析器方面进行工作。 我们从范围和变量的概念开始。在进行一些搜索时,我们发现了一个示例,其中每个作用域都有一个符号表和一个指向上一个作用域的链接,因此,如果在当前作用域中找不到变量,它将在上一个作用域中查找,依此类推。 从这个例子中,我必须指出的第一件事是,尝试访问堆栈中多个作用域甚至不存在的变量会有很高的成本(最坏的情况是堆栈深度)。我想…不,我们可以做得更好

Parsing 制作玩具语言解释器、AST变量和作用域 标题中,我最近开始和C++的一个C++解析器/解释器合作,稍后将被集成在一个更大的项目中(如果它工作);我们决定开始制作用于抽象语法树的类,稍后我们将在适当的解析器方面进行工作。 我们从范围和变量的概念开始。在进行一些搜索时,我们发现了一个示例,其中每个作用域都有一个符号表和一个指向上一个作用域的链接,因此,如果在当前作用域中找不到变量,它将在上一个作用域中查找,依此类推。 从这个例子中,我必须指出的第一件事是,尝试访问堆栈中多个作用域甚至不存在的变量会有很高的成本(最坏的情况是堆栈深度)。我想…不,我们可以做得更好,parsing,abstract-syntax-tree,interpreter,Parsing,Abstract Syntax Tree,Interpreter,我们思考的结果如下: 程序根目录下的单个符号表,由字符串映射到变量堆栈组成 map<string, stack<variable>> table; 这种方式在任何情况下都会进行分配、读写O(1)。 下面是我的两个问题: 1) 与只需删除一个数组的多表系统相比,必须为表和作用域中的每个变量保存字符串,并且必须在作用域的末尾循环所有变量,这是否效率低下 2) 我发现的例子并不是很有效率,因为它是一个非常初级的教程,或者我缺少的东西比我和我朋友提出的想法更有价值?这里没有绝对

我们思考的结果如下: 程序根目录下的单个符号表,由字符串映射到变量堆栈组成

map<string, stack<variable>> table;
这种方式在任何情况下都会进行分配、读写O(1)。 下面是我的两个问题:

1) 与只需删除一个数组的多表系统相比,必须为表和作用域中的每个变量保存字符串,并且必须在作用域的末尾循环所有变量,这是否效率低下


2) 我发现的例子并不是很有效率,因为它是一个非常初级的教程,或者我缺少的东西比我和我朋友提出的想法更有价值?

这里没有绝对的答案;这取决于语言中作用域的精确性质以及编程风格。我的建议是先让它工作起来,然后看看是否需要改进。无论您选择使用什么符号表实现,请确保将实现细节隐藏在ADT原型后面,ADT原型将定义符号表的行为。然后,如果需要,您可以轻松地在不同的实现中进行交换

无论如何,这里有几个数据点:

  • 范围嵌套通常不是很深。事实上,对于大多数语言来说,深度嵌套的作用域被认为是糟糕的风格

  • 您的建议涉及为每个作用域创建一个哈希表。这不是真的必要;您可以使用一个用于所有查找的哈希表和一个标记范围边界的堆栈来完成整个工作。符号表是一个
    无序映射
    ,范围堆栈是
    堆栈
    。(我假定C++)。这里,代码>名称<代码>可能是<代码> STD::String 的别名,但请参见下面。<代码>定义< /COD>包含每个符号需要存储的元数据。不必将它们分开;可以使用单一类型,然后使用<代码> SET/COM> >而不是<代码> MAP< /C> >。作用域堆栈中的
    定义
    是来自某个外部作用域的定义,或者表示在外部作用域中变量未定义。还需要有一个sentinel值(用于名称或定义),用于指示范围的开始

    当您输入范围时,您将哨兵推到范围堆栈上。然后,每次定义一个变量时,它的前一个定义都会推送到作用域堆栈上,新的定义会存储到符号表中。离开作用域时,将作用域堆栈弹出到最后一个sentinel,并在执行时将每个变量替换为其以前的定义

  • 在一种典型的语言中有许多不同类型的作用域。这里有几个例子:

    • 闭包范围。如果允许在函数内部定义函数,那么一些外部作用域实际上是闭包。虽然符号表处理与正确跟踪元数据没有什么区别,但它们需要不同于同一函数中外部块中的作用域的处理

    • 全局作用域和/或模块作用域

    • 复合对象(“类”)成员名称范围。它们与块作用域的嵌套方式不同,但根据您的语言的名称查找算法,它们可能仍然是链式名称搜索的一部分

  • 显然,创建名称
    std::string
    对象更简单,但最终会创建大量重复字符串,与其他字符串相比,这些字符串需要是string。现代计算机的速度足够快,这些都无关紧要,但无论如何,你可能想对其进行优化。我更喜欢通过将字符串放入
    std::set
    (或等效项)中,然后使用元素指针而不是字符串本身来“实习”字符串。这有两个好处:

    • 每个字符串只存储和分配一次,这节省了分配开销。现代的分配库速度相当快,但保存同一字符串的无数个副本仍然毫无意义,在程序中使用同一名称时,每个副本一个。将名称保留在intern表中可能会增加它们的生命周期,但实际上这并不是什么问题,特别是因为许多程序员在不同的范围内循环使用名称

    • 可以通过指针比较而不是逐个字符比较来比较名称。这要快一点,因为它不需要循环。同样,现代硬件使这变得不必要,但它仍然是一个加号。如果随后使用指针而不是字符串作为符号表键,则可以节省每次查找时计算键哈希的开销。这是另一个可衡量但不是革命性的进步


  • 这里没有绝对的答案;这取决于语言中作用域的精确性质以及编程风格。我的建议是先让它工作起来,然后看看是否需要改进。无论您选择什么作为符号表实现,请确保将实现细节隐藏在ADT原型后面,这将
    set<string> allocated;
    
    for(variable_name in allocated)
        {
        table[variable_name].pop();
        }