概述
编译程序把源程序翻译为目标程序。根据源程序的语言种类,翻译程序可以分为汇编程序与编译程序。与之相对,解释程序是对源程序进行解释执行的程序。相应的可以将高级语言分为
- 编译型 C/C++, Swift, etc.
- 解释型 Python, javascript, etc.
- 混合型 Java, etc.
本文重点放在编译程序的设计上。典型的编译程序具有 7 7 7 个逻辑部分
对源程序扫描一次被称为一遍 (pass)。典型的一遍扫描编译程序有如下形式
通常将中间代码生成前的分析部分称为编译器的前端,其后的综合部分则被称为后端。这样就把一个编译程序分为了与源语言相关和与目标机有关的两个独立的部分,降低了程序的耦合。假设 llvm 编译器 支持 M M M 种源语言到 N N N 种目标语言的编译
传统的编译器如 gcc
可能需要开发
M
×
N
M \times N
M×N 个不同的子模块。而 llvm
使用统一的中间语言 llvm Intermediate Representation
只需要
M
M
M 个前端与
N
N
N 个后端,大大降低了开发成本。
文法
设非空有穷集合 Σ \Sigma Σ 为一字母表,则其上的符号串为 ∀ s ∈ Σ ∗ \forall s \in \Sigma^* ∀s∈Σ∗ ,其中 ∗ * ∗ 表示集合的闭包。特别的记 Σ 0 = ε \Sigma^0 = {\varepsilon} Σ0=ε 为空串组成的集合。规则通常写作
U : : = x or U → x , ∣ U ∣ = 1 , ∣ x ∣ ≥ 0 U ::= x\text{ or }U\rightarrow x,\quad |U| = 1, |x| \ge 0 U::=x or U→x,∣U∣=1,∣x∣≥0
其中左部 U U U 是符号,右部 x x x 是有穷符号串。规则的集合 P P P 即可确定一个文法 G G G
<程序> ::= <常量说明><变量说明><函数说明>
<常量说明> ::= {const<常量定义>;}
<常量定义> ::= int<标识符>=<整数>{,<标识符>=<整数>}|char<标识符>=<字符>{,<标识符>=<字符>}
<变量说明> ::= {<类型标识符><变量定义>;}
<变量定义> ::= <标识符>[<下标>]{,<标识符>[<下标>]}
<下标> ::= '['<无符号整数>']' // <无符号整数>表示数组元素的个数,其值需大于0
<函数说明> ::= {(<类型标识符>|void)<函数定义>}void<主函数>
<函数定义> ::= <标识符>'('<参数表>')'<复合语句>
<参数表> ::= [<类型标识符><标识符>{,<类型标识符><标识符>}]
<主函数> ::= main'('')'<复合语句>
<复合语句> ::= '{'<常量说明><变量说明>{<语句>}'}'
<语句> ::= <条件语句>|'{'{<语句>}'}'|<函数调用语句>;|<赋值语句>;|<读语句>;|<写语句>;|<返回语句>;|;
<条件语句> ::= <if语句>|<while语句>|<do语句>|<for语句>
<if语句> ::= if'('<条件>')'<语句>[else<语句>]
<while语句> ::= while'('<条件>')'<语句>
<do语句> ::= do<语句>while'('<条件>')'
<for语句> ::= for'('<标识符>=<表达式>;<条件>;<标识符>=<标识符><加法运算符><无符号整数>')'<语句>
<条件> ::= <表达式>[<关系运算符><表达式>] // 表达式为0条件为假,否则为真
<函数调用语句> ::= <标识符>'('[<表达式>{,<表达式>}]')'
<赋值语句> ::= <标识符>['['<表达式>']']=<表达式>
<读语句> ::= scanf'('<标识符>{,<标识符>}')'
<写语句> ::= printf'('<字符串>[,<表达式>]')'|printf'('<表达式>')'
<返回语句> ::= return['('<表达式>')']
<表达式> ::= [<加法运算符>]<项>{<加法运算符><项>} // [+|-]只作用于第一个<项>
<项> ::= <因子>{<乘法运算符><因子>}
<因子> ::= <标识符>['['<表达式>']']|'('<表达式>')'|<整数>|<字符>|<函数调用语句>
<整数> ::= [<加法运算符>]<无符号整数>
<标识符> ::= <字母>{<字母>|<数字>}
<无符号整数> ::= <非零数字>{<数字>}|0
<数字> ::= 0|<非零数字>
<非零数字> ::= 1|...|9
<字符> ::= '<加法运算符>'|'<乘法运算符>'|'<字母>'|'<数字>'
<字符串> ::= "{十进制编码为32,33,35-126的ASCII字符}"
<类型标识符> ::= int|char
<加法运算符> ::= +|-
<乘法运算符> ::= *|/
<关系运算符> ::= <|<=|>|>=|!=|==
<字母> ::= _|a|...|z|A|...|Z
上述文法使用扩充的 BNF 表示法进行描述
符号 | 定义 | 说明 |
---|---|---|
∣ \vert ∣ | 或 | 作用域由括号限定 |
{ t } n m \{t\}^m_n {t}nm | 将 t t t 重复连接 n ∼ m n \sim m n∼m 次 | 缺省时 m = ∞ , n = 0 m = \infin,\ n = 0 m=∞, n=0 |
[ t ] [t] [t] | 符号串 t t t 可有可无 | 等价于 { t } 1 \{t\}^1 {t}1 |
( t ) (t) (t) | 局部作用域 | 主要用于限定 ∣ \vert ∣ 范围 |
相关概念有
概念 | 符号 | 定义 | 示例 |
---|---|---|---|
识别符号 | Z Z Z | 文法中第一条规则的左部符号 | <程序> |
字汇表 | V V V | 文法中出现的全部符号 | { <程序>, <常量说明>, …, 0, 1, … } |
非终结符号集 | V n V_n Vn | 全部规则的左部组成的集合 | { <程序>, <常量说明>, <变量说明>, … } |
终结符号集 | V t V_t Vt | V − V n V - V_n V−Vn | { 0, 1, …, _, a, b, … } |
设 U : : = u ∈ P U ::= u \in P U::=u∈P 则对于 ∀ x , y ∈ V ∗ \forall x, y \in V^* ∀x,y∈V∗ 有直接推导 x U y ⇒ x u y xUy \Rightarrow xuy xUy⇒xuy 。如果 y ∈ V t ∗ y \in V_t^* y∈Vt∗ 则 x U y ⤃ x u y xUy\ ⤃\ xuy xUy ⤃ xuy 称为规范推导。直接推导序列 u 0 ⇒ u 1 ⇒ ⋯ ⇒ u n u_0 \Rightarrow u_1 \Rightarrow \cdots \Rightarrow u_n u0⇒u1⇒⋯⇒un 可简记为
{ u 0 ⇒ + u n n > 0 u 0 ⇒ ∗ u n n ≥ 0 \begin{cases} u_0 \mathop\Rightarrow\limits^+ u_n & n > 0\\ u_0 \mathop\Rightarrow\limits^* u_n & n \ge 0\\ \end{cases} {u0⇒+unu0⇒∗unn>0n≥0
进一步定义
- 句型 V ∗ ∋ x ⇐ ∗ Z V^* \ni x \mathop\Leftarrow\limits^* Z V∗∋x⇐∗Z
- 句子 V t ∗ ∋ x ⇐ + Z V_t^* \ni x \mathop\Leftarrow\limits^+ Z Vt∗∋x⇐+Z
- 语言 L ( G ) = { x ∣ x is sentence } L(G) = \{ x| x\text{ is sentence} \} L(G)={x∣x is sentence}
如果文法 G G G 和 G ′ G' G′ 有 L ( G ) = L ( G ′ ) L(G) = L(G') L(G)=L(G′) ,则称这两个文法等价。设 w = x u y w=xuy w=xuy 为一句型,称 u u u 为一个相对于 U ∈ V n U \in V_n U∈Vn 的
- w w w 的短语 如果 Z ⇒ ∗ x U y ∧ U ⇒ + u Z \mathop\Rightarrow\limits^* xUy \land U \mathop\Rightarrow\limits^+ u Z⇒∗xUy∧U⇒+u
- w w w 的简单短语 如果 u u u 是短语且 U ⇒ u U \mathop\Rightarrow\limits u U⇒u
句型的最左简单短语称为句柄。
二义性
文法 G G G 是二义性的,如果 ∃ x ∈ L ( G ) \exist x \in L(G) ∃x∈L(G) 使下列条件之一成立
- x x x 可以对应两颗不同的语法树
- x x x 有两个不同的规范推导
词法(线性)分析
扫描源程序字符,按词法规则识别单词,同时进行词法检查
单词是语言的基本语法单位
种类 | 属性类型 | 属性值 |
---|---|---|
IDENFR | string | 标志符名称 |
INTCON | int | 无符号整数值 |
CHARCON | char | 字符常量 |
STRCON | string | 字符串常量 |
RESERVED | Reserved | CONSTTK, INTTK, CHARTK, VOIDTK, MAINTK, IFTK, ELSETK, DOTK, WHILETK, FORTK, SCANFTK, PRINTFTK, RETURNTK |
DELIM | Delim | ASSIGN, SEMICN, COMMA, LPARENT, RPARENT, LBRACK, RBRACK, LBRACE, RBRACE |
OPER | Oper | PLUS, MINU, MULT, DIV |
COMP | Comp | LSS, LEQ, GRE, GEQ, EQL, NEQ |
语法(层次)分析
根据文法分析并识别出各种语法成分,并进行正确性检查
语义分析
对语法树进行语义分析,产生相应的中间代码
中间代码是一种介于源语言和目标语言之间的语言形式,常用的有四元式、逆波兰表示等。
代码优化
生成高质量的目标程序
生成目标程序
由中间代码生成目标程序
符号表管理
把源程序中的信息和编译过程中所产生的信息登记在表格中,便于在随后的编译过程中进行查找
错误处理
诊断出源代码的错误,并报告用户错误的性质和位置