C#龙书(词法分析)如何处理字面量

C# Dragon Book (Lexical analysis) How to handle literals

这个项目用于教育用途,我很清楚优秀的编译器已经存在。

我目前正在努力通过著名的 Dragon Book,并且刚刚开始实施我自己的词法分析器。除了文字之外,它工作得非常好。我不明白如何使用符号(查找)tables 处理文字,而且这本书似乎没有很好地涵盖:

在下面的代码中 60 是一个数字文字:

int myIdentifier = 60;

龙书说:

Technically speaking, for the lexeme 60 we should make up a token like (number,4), where 4 points to the symbol table for the internal representation of integer 60 [...]

了解 - 我创建了以下令牌:

<enum TokenType, int lookupIndex> 
//TokenType could be 'number' and lookupIndex could be any int

并将文字存储在这样的字典中:

Dictionary<int literal, int index> 
//literal could be '60' and index could be anything

由于文字本身是字典中的键,这让我可以快速检查未来的文字是否已经添加到符号 table(或没有)。

解析器然后从词法分析器接收令牌并且应该能够识别符号 table.
中的文字
问题:

  1. 为什么我的令牌应该包含查找索引而不是文字本身?
    那样不是更快吗...
  2. 当 lookup-index 是字典的值时,Parser 应该如何快速找到 symbol-table 中的文字值?
    (我不能使查找索引成为字典键,因为 Lexer 将不得不检查字典的值,这也不是很有效)
    多索引词典可以成为解决方案吗?我猜不是...
  3. 我必须为每种类型的文字创建一个符号-table吗?
    F.e.:Dictionary<int literal, int index>
    Dictionary<double literal, int index>
    Dictionary<char literal, int index> 等等
  4. 也许我在文字方面完全走错了路。欢迎post任何更好的解决方案。
  1. 为什么我的 Token 应该包含查找索引而不是文字本身?那不是更快吗?

    当然,这可能会更快。但是每个文字都会有不同的值。现在,大多数程序员都期望如果他们在同一个程序中使用 "this longish string" 两次,编译器将足够聪明,只在最终的 executable 中发出该字符串的一个副本.如果在反编译代码时发现常量 1 有 273 个不同的存储位置,那么我们应该说 令人惊讶 ,因为每次编译器看到 a += 1,它创建了一个新常量。

    确保常量文字仅发出一次的最简单方法是将它们保存在由文字值索引的关联容器中。

    正如@sepp2k 在评论中指出的那样,大多数硬件允许使用小整数常量作为直接操作数,有时甚至是不太小的常量。所以上面关于常数 1 的说法有点夸张。您可能能够以不同的方式处理整数,但这可能不值得麻烦。

  2. 当lookup-index为字典的值时,Parser如何快速找到符号-table内的文字值?

    这在很大程度上取决于您用于文字 tables 的精确数据结构(我不喜欢将其称为符号 tables,但不可否认这些概念是相关的。)在许多语言,你会发现你的标准库容器不是问题的完美匹配,所以你要么需要调整它们以适应目的,要么编写替换。

    不过,它并不是非常复杂。一种可能性是使用 map<literalType, int>vector<literalType> 的组合。在这里,映射将文字值与向量中的索引相关联。当你找到一个新的文字值时,你将它输入到与向量当前大小相关联的映射中,然后将该值压入向量(这将使其索引对应于你刚刚插入到映射中的索引。)

    这对于像字符串这样的大常量来说并不完全理想,因为在映射中的键和向量中的值之间,常量存储了两次。当您开始时,我建议您压抑对这种重复的烦恼;后面如果证明有问题,再想办法解决。

    如果您使用的是 C++,则可以使用(无序)集合而不是映射,并使用对新添加元素的引用(指针)而不是索引。但我不认为该功能在许多语言中都可用,而且与索引相比,指针有时也很尴尬。在某些语言中,您可以将所有值放入向量中,然后保留一个集合,其 keys 是向量中的索引。这要求可以使用键类型以外的其他方式来查找集合;出于某种原因,此功能在极少数数据结构库中可用。

    而且,是的,可以使用双索引数据结构,如果您手头有其中一个的话。 (实际上,map+vector 解决方案是一个双索引数据结构。)

  3. 我必须为每种类型的文字创建一个符号-table吗?

    也许吧。你有多少种文字?

    您最终可能会为变量和常量使用带类型标记的枚举 ("discriminated unions")。 (同样,并非所有语言在其标准库中都有可区分的联合,这确实令人难过;如果您的实现语言缺少此基本功能,您将需要实现它。)当然应该可以使用可区分的联合实例作为关联数据结构中的键,因此原则上没有什么可以阻止您将所有文字保存在单个数据结构中。如果您有合适的类型,那绝对是我推荐的,至少在开始时是这样。

    请注意,当您最终将文字作为目标代码发出时,您实际上对它们的位表示和对齐比它们的语义更感兴趣。如果两个完全不同类型的常量碰巧具有相同的位表示,那么您可以为它们使用相同的存储位置。如果您有多种宽度的整数数据类型,那么您可能希望将它们全部保存在一个文字 table 中,正是为了利用这种优化。无需存储每个宽度的 1 :)。有时您会发现其他情况,其中两个不同类型的文字具有相同的表示形式,但这种情况可能还不够普遍,您不必费心去处理它。 (但是,在 IEEE 硬件上,浮点数和整数零具有相同的表示形式,通常与 NULL 指针的表示形式相同,因此您可能需要特殊情况的零。)

    总而言之,这是一个判断电话。使用可区分的联合作为密钥有多复杂?使用具有特定键类型的关联容器可以节省多少存储空间,这重要吗?您是否想要遍历相同类型的所有文字常量(答案:可能),这对您的数据结构来说有多容易?

    如果您使用设计良好的内部 API,您将能够改变对文字 table 的内部表示的看法。所以我会利用这个实验作为尝试好的 API 设计的机会。

  4. 还有什么吗?

    祝你的项目好运。学习并享受!