作用域规则

标识符从其声明点开始有效,直到声明发生的块结束。 标识符已知的范围是标识符的范围。 标识符的确切范围取决于它的声明方式。

块作用域

在块的声明部分中声明的变量的 作用域 从声明点到块结束有效。 如果块包含第二个块,其中标识符被重新声明,则在该块内,第二个声明将是有效的。 跳出内部区块后,第一个声明再次有效。 除非对过程或迭代器重载有效,否则不能在同一个块中重新定义标识符。

元组或对象作用域

元组或对象定义中的字段标识符在以下位置有效:

  • 到元组/对象定义的结尾。
  • 给定元组/对象类型的变量的字段指示符。
  • 在对象类型的所有后代类型中。

模块作用域

模块的所有标识符从声明点到模块结束都是有效的。 来自间接依赖模块的标识符 可用。 The system 模块自动导入每个模块。

如果模块通过两个不同的模块导入标识符,则必须限定每次出现的标识符,除非它是重载过程或迭代器,在这种情况下会发生重载解析:

  1. # 模块A
  2. var x*: string
  1. # 模块B
  2. var x*: int
  1. # 模块C
  2. import A, B
  3. write(stdout, x) # 错误: x有歧义
  4. write(stdout, A.x) # 没有错误: 使用限定符
  5.  
  6. var x = 4
  7. write(stdout, x) # 没有歧义: 使用模块C的x

代码重排

注意 :代码重新排序是实验性的,必须通过 {.experimental.} 启用。

代码重新排序功能可以在顶级范围内隐式重新排列过程,模板和宏定义以及变量声明和初始化,这样在很大程度上,程序员不必担心正确排序定义或被迫使用转发声明前缀模块内的定义。

注意:以下是代码重新排序前体的文档,即 {.noForward.} 。

在此模式下,过程定义可能不按顺序出现,编译器将推迟其语义分析和编译,直到它实际需要使用定义生成代码。 在这方面,此模式类似于动态脚本语言的操作方式,其中函数调用在代码执行之前不会被解析。 以下是编译器采用的详细算法:

1. 次遇到可调用符号时,编译器将只记录符号可调用名称,并将其添加到当前作用域中的相应重载集。 在此步骤中,它不会尝试解析符号签名中使用的任何类型表达式(因此它们可以引用其他尚未定义的符号)。

2. 当遇到顶级调用时(通常在模块的最末端),编译器将尝试确定匹配的重载集中所有符号的实际类型。 这是一个潜在的递归过程,因为符号的签名可能包含其他调用表达式,其类型也将在此时解析。

3. 最后,在选择最佳重载之后,编译器将启动编译各自符号的正文。这反过来将导致编译器发现需要解决的更多调用表达式和步骤必要时将重复图2和3。

请注意,如果在此方案中从未使用过可调用符号,则为身体永远不会编译。这是导致最佳的默认行为编译时间,但如果所有定义都是详尽的汇编必需的,使用 nim check 也提供此选项。

示例:

  1. {.experimental: "codeReordering".}
  2.  
  3. proc foo(x: int) =
  4. bar(x)
  5.  
  6. proc bar(x: int) =
  7. echo(x)
  8.  
  9. foo(10)

变量也可以重新排序。 初始化 的变量(即将声明和赋值组合在一个语句中的变量)可以重新排序其整个初始化语句。 小心顶级执行代码:

  1. {.experimental: "codeReordering".}
  2.  
  3. proc a() =
  4. echo(foo)
  5.  
  6. var foo = 5
  7.  
  8. a() # 输出: "5"

重要的是要注意,重新排序 适用于顶级范围的符号。因此,以下将 编译失败

  1. {.experimental: "codeReordering".}
  2.  
  3. proc a() =
  4. b()
  5. proc b() =
  6. echo("Hello!")
  7.  
  8. a()