Compiler construction 解决循环进口问题

Compiler construction 解决循环进口问题,compiler-construction,programming-languages,interpreter,language-design,abstract-syntax-tree,Compiler Construction,Programming Languages,Interpreter,Language Design,Abstract Syntax Tree,好的,我目前正在编写一个解释器(针对我自己设计的语言),在处理imports时遇到了一个问题 下面是解释器的工作原理: 一次导入语句被执行,somefile被加载、解析并执行 这意味着,如果在somefile中声明了一个函数/类,则在全局函数表中创建了一个条目,以便从那时起可以调用它 然后我们进入下一个声明。并执行它 现在问题来了 第1期: 假设在文件file1中有一个doSth函数,并导入到file2 如果有file3导入file2,这意味着file1的操作也可用。这是不可取的 第二期

好的,我目前正在编写一个解释器(针对我自己设计的语言),在处理
import
s时遇到了一个问题

下面是解释器的工作原理:

  • 一次
    导入语句被执行,
    somefile
    被加载、解析并执行
  • 这意味着,如果在
    somefile
    中声明了一个函数/类,则在全局函数表中创建了一个条目,以便从那时起可以调用它
  • 然后我们进入下一个声明。并执行它
现在问题来了

第1期:

  • 假设在文件
    file1
    中有一个
    doSth
    函数,并导入到
    file2
  • 如果有
    file3
    导入
    file2
    ,这意味着
    file1
    的操作也可用。这是不可取的
第二期:

  • 如果
    file1
    导入
    file2
    (因为,假设我们需要在
    file2
    中使用函数)和
    file2
    导入
    file1
    ,这将导致循环引用(很可能会一直运行到时间结束)

那么,如何解决这些问题呢?有什么想法吗


我知道这个问题非常专业和复杂,但是如果你知道现有的解释器/编译器是如何处理这个问题的,请解释一下!;-)

循环依赖仍然是困扰C的一个问题。如果文件已经加载,可以使用预处理器跳过包含来解决这个问题。像这样:

#ifndef __FOO_H
#define __FOO_H

// other #include's go here

#endif // __FOO_H

第1期

C和朋友们表现出的正是你抱怨的行为,表明无论它多么不受欢迎,人们都有可能生活在一起:)然而,许多其他语言都有与你相同的观点。我所知道的所有解决方案都是“申报出口”的一些变体。通常,您需要一些机制来指定哪些定义被导出(或哪些定义不被导出);如果您需要显式地重新导出导入的定义,那么您将不再受到默认重新导出的影响

但是,现在您会遇到一个稍微不同的问题:

file3:
  export cool_definition

file2:
  import file3
  export bland_definition
  /* Use cool_definition */

file1:
  import file2 /* Note: only bland_definition is imported */
  import file3 /* We want cool_definition */
现在,您可能必须确保直接在
file1
中使用
cool\u定义时与在
file2
中使用
bland\u定义时具有相同的含义。(取决于您的语言的状态,以及您对消除代码重复的关注程度等)

第二期

假设您同意预处理器导入保护是一种荒谬的黑客行为,那么您需要记住哪些文件已经导入,如果文件已经导入或正在导入,则拒绝导入。这是最简单的部分

使循环导入链起作用意味着您需要能够导入文件,而不知道它可能依赖的定义。例如,如果这些定义是改变程序语法的宏,那么这可能很棘手。否则,您通常可以收集所有导入的传递闭包;一次处理一个文件以发现定义;然后解析定义(并执行编译所需的其他操作)


Google protobuf编译器的源代码中有一个相当可读的策略实现(我想您可以开始浏览,但我已经有一段时间没有看过该代码了。)

有两种完全不同的情况需要考虑。一些有趣的选择包括:

  • 导入的文件被认为只是插入当前编译中的文本。
    • 包含的文件会影响编译时状态,因此包含的顺序很重要
    • 只要一切都得到定义,包含的顺序就不重要了
  • 导入的文件是单独编译的,但可能会将定义“泄漏”到封闭范围中
  • 导入的文件有自己的模块结构/词法范围,因此它只提供导出的定义
  • 避免多次包含同一个文件是很简单的,但是如果您有顺序依赖关系,那么除非您管理编译时状态,否则会产生严重的副作用。实际情况是,包含原始源代码最终导致的问题比它解决的问题还要多

    我强烈倾向于只提供(3)。无论文件是原始源文件、预编译文件,甚至是用完全不同的语言编写的文件,导入都应该是一样的。它应该提供一组简单的定义供使用

    将全局符号表中的条目作为访问导入函数的唯一方法,这确实不是一个好主意。导入的文件应提供作用域,以便避免命名冲突。其目的是,如果A输入B,B输入C,那么A可以调用B,但不能调用C


    如果您正在寻找可以为您的行为建模的语言,那么从忽略C/C++和Javascript开始(或者将它们视为不应该做的事情的示例)。大多数现代语言都提供了一个合理的模型,这取决于您正在尝试做什么。

    您需要研究拓扑排序。看起来您忘记设计语言的模块系统了?该语言用于什么?可以说的更多,但我们缺乏背景。