# 第 1 章：源码、绑定与作用域

## 语法摘要

| 形式 | 语法 | 用途 |
| --- | --- | --- |
| 标号 | `name:` | 在当前逻辑地址定义源码标号。 |
| 处理器指令 | `mnemonic operands` | 写出当前目标平台的普通指令。 |
| 常量 | `const name [: type] = expression` | 声明不可变的编译期值。 |
| 变量 | `let name [: type] = expression` | 声明可变的编译期值。 |
| 赋值 | `name = expression` | 更新已经存在的可变绑定。 |
| 代码块 | `{ statements }` | 建立嵌套的词法作用域。 |
| 跨行参数 | `callee(` ... `)` | 让一个表达式跨越多行继续书写。 |

## 标号

```asm
// 在当前位置定义 start 标号，并写出一条设置 rax 的指令。
start:
    mov rax, 1
```

标号会把名称绑定到它在源码位置对应的逻辑地址。后续指令、表达式、地址回填项和符号 API 都可以引用标号。只要所选指令编码或输出操作能够由汇编流程解析，就允许向前引用尚未定义的标号。

标号不是编译期的 `const` 或 `let` 绑定。它表示汇编器符号，最终数值取决于布局结果。

## 处理器指令

```asm
// 使用普通指令语法写出三条 x86-64 指令。
mov rax, 1
add rax, 2
ret
```

处理器指令使用正常的文本语法，不写成编译期函数调用。当前目标平台决定可以使用哪些助记符、寄存器、操作数和指令编码。

编译期语句和处理器指令可以出现在同一个源文件中。编译期表达式决定写出哪些内容，处理器指令行描述要写出的指令。

## 常量

```asm
// 声明一个由类型推断的常量和一个显式指定类型的常量。
const page_size = 4096
const marker: u8 = 0x90
```

`const` 会计算初始化表达式，并在当前作用域中建立不可变绑定。当值的类型可以推断时，可以省略显式类型。

不能向常量赋值：

```text
const value = 1
value = 2
```

配置值、计算得到的地址、描述项数值以及其他不应在声明后改变的信息，适合使用 `const`。

## 变量

```asm
// 建立两个可变绑定，再分别更新它们。
let count = 1
let flags: u32 = 0

count = count + 1
flags = flags | 0x20
```

`let` 会在当前作用域中建立可变绑定。赋值会更新具有指定名称且距离当前位置最近的可见可变绑定。

赋值不会声明新名称。目标绑定必须已经存在，而且必须可变。

## 固定位宽整数类型

| 类型族 | 类型 | 可接受的值 |
| --- | --- | --- |
| 无符号 | `u8`、`u16`、`u32`、`u64` | `0` 到 `2^位宽 - 1` |
| 有符号 | `i8`、`i16`、`i32`、`i64` | `-2^(位宽 - 1)` 到 `2^(位宽 - 1) - 1` |

这些类型可以用于 `const` 和 `let` 绑定、函数参数与返回值，以及结构体和联合体中的整数字段。聚合值被打包或写出时，有符号值使用补码表示。

```asm
// 有符号位移和无符号计数都带有明确的宽度约束。
const displacement: i32 = -4
let retry_count: u16 = 3
```

初始化值超出声明范围时会报错。

## 代码块与名称遮蔽

```asm
// 内层代码块使用同名变量，但不会改变外层常量。
const value = 1

{
    let value = 2
    value = value + 1
    assert(value == 3)
}

assert(value == 1)
```

花括号会建立词法作用域。代码块中的声明可以遮蔽外层作用域中的同名绑定，而不会改变外层绑定。

代码块结束后，其中声明的绑定不再存在：

```text
{
    let hidden = 1
}

emit.u8(hidden)
```

代码块适合限制临时值的可见范围、隔离辅助计算，并明确表示名称遮蔽。

## 跨行表达式参数

只要调用表达式的圆括号尚未闭合，就可以跨越多行继续书写：

```asm
// 在多行中书写同一个 assert 调用。
const value = 1

assert(
    value == 1
)
```

缩进可以提高可读性，但不能代替右侧结束符。嵌套调用也可以使用相同形式。只有全部结束符闭合后，完整表达式才会求值。

## 完整示例

下面的源代码组合了本章介绍的七种形式：

```asm
// 外层 value 是不可变绑定。
const value = 1

// 内层 value 是只在代码块中存在的可变绑定。
{
    let value = 2
    value = value + 1
    assert(value == 3)
}

// 跨行检查外层绑定仍然保持原值。
assert(
    value == 1
)

// 定义标号并写出指令，最后写出外层 value 的一个字节。
start:
    mov rax, 1

emit.u8(value)
```

源代码会写出：

```text
b8 01 00 00 00 01
```

内层 `value` 可以修改，而且只在代码块中可见。外层 `value` 仍然是不可变值 `1`，并在指令之后写入输出内容。

## 选用指南

| 需求 | 使用形式 |
| --- | --- |
| 绑定由布局产生的地址 | 标号 |
| 描述目标平台指令 | 处理器指令行 |
| 命名不可变的编译期信息 | `const` |
| 保存可变的编译期状态 | `let` |
| 更新可变状态 | 赋值 |
| 限制名称的可见范围或遮蔽名称 | 代码块 |
| 整理较长的调用或嵌套表达式 | 跨行参数 |
