编译原理

编译原理

敬请T期待 Lv3

编译器

编译器是把源程序从一种表示变成另一种表示。

编译原理的四大部分:前端词法分析语法分析语义分析后端代码生成

编译器 具有非常 模块化的高层结构

词法分析

词法分析:检查字符流的单词合法性,生成记号流。【构成源程序的字符流,按照编程语言的词法规则将字符流组成词法记号流(token)】

字符流:和被编译的语言密切相关

记号流:编译器内部定义的数据结构,编码所识别出的词法单元。

词法分析器的实现方式:

  1. 手工编码实现法
    • 先对复杂,容易出错
    • 目前较为流行
  2. 词法分析器的生成器
    • 可以快速原型,代码量较少
    • 较难控制细节

有限状态自动机:

  1. 确定状态有限状态自动机DFA
    • 对任意的字符,最多有一个状态可以转移
  2. 非确定的有限状态自动机NFA
    • 对任意的字符,有多于一个状态可以转移

自动生成的过程:

正规表达式-(Thompson算法)–>NFA-(子集构造法)–>DFA-(Hopcroft 最小化算法)–>词法分析器代码

Thompson算法:

Thompson1 Thompson2

DFA的邻接矩阵表示法

DFA的临接矩阵表示法

状态|字符 a b
0 1 0
1 2 1
2 2 2

a(b|c)^*^ NFA

a(b|c)\*

子集构造法:

q0={n0}

q0-a->{n1,n2,n3,n4,n6,n9}=q1

q1-b->{n4,n5,n8,n9,n3,n6}=q2

q1-c->{n6,n7,n8,n9,n3,n4}=q3

q2-b->{n4,n5,n8,n9,n3,n6}=q2

q2-c->{n6,n7,n8.n9,n3,n4}=q3

q3-b->{n4,n5,n8,n9,n3,n6}=q2

q3-c->{n6,n7,n8,n9,n3,n4}=q3

a(b|c)^*^ DFA

a(b|c)\*

状态|字符 a b c
q0 q1 \ \
q1 \ q2 q3
q2 \ q2 q3
q3 \ q2 q3

Hopcroft 算法:

1
2
3
4
5
6
7
8
9
10
//基于等价类的思想
split(S)
foreach(character c)
if(c can split s)
split s into T1, ..., Tk

hopcroft()
split all nodes into N, A
while(set is still changes)
split(s)

Hopsroft 算法就是先根据非终结状态与非终结状态将所有的节点分为 N 和 A 两大类。 N 为非终结状态,A 为终结状态,之后再对每一组运用基于等价类实现的切割算法。

切分为 N 和 A, N 是 q0, A 是 {q1, q2, q3};在 A 中,就字符 b 的状态转移,每个节点最后得到的都还是 A 这个状态,无法对 q1, q2, q3 进行区分。同理,c 也不能这三个节点进行切分。所以就将这三个节点融合为一个新的节点 q4。

最简DFA.png

状态跳转表

状态\字符 a b c
q0 q4 \ \
q4 \ q4 q4

例题:

作业2.2:在下面的C函数钟,按序列出所有的记号,并给每个记号以合理的属性值

1
2
3
4
5
6
7
8
long gcd(long p long q){
if (p%q==0)
/*then part*/
return q;
else
/*else part*/
return gcd(q,p%q);
}

一共有33组记号

关键字:long 、 if 、 else 、 return

运算符:% 、 == 、 0

标识符:gcd 、p 、q

界限符:,、{、}、(、)

作业2.3:叙述下列正规式描述的语言

(a)

以0开头和结尾的由0和1组成的串

(b)

包含空串的由0和1组成的串

(c)

倒数第三个字符为0的由0和1组成的串

(d)$$0^10^10^10^$$

只含有三个1,由0和1组成的串

(e)

由偶数个0和偶数个1组成的所有0和1的串

(f)

不包含011子串的所有由0和1组成串

(g)$$(01|1)^0^$$

不包含001子串的所有由0和1组成的串

例题2.4:为下列语言写出正规定义

例题:C语言中无符号整数如何用正则表达式表示?

(1|2|……|9)(0|1|2|……|9)*|0

例题:C语言中的标识符:以字母或下划线开头,后面跟0个或多个字母、数字、下划线。如何用正则表达式表示。

(a|b|……|z|A|……|Z|_)(0|1|…|9|a|…..|z|A.…..|Z|_)

例题:将下图(a|b)*ab的NFA转换为DFA,并画出状态转移表

demo_NFA

n0={0,1,2,4,7}=A

A–a–>{3,6,7,8,1,2,4}=B

A–b–>{5,6,7,1,2,4}=C

B–a–>{3,6,7,8,1,2,4}=B

B–b–>{5,6,7,9,1,2,4}=D

C–a–>{3,6,7,8,1,2,4}=B

C–b–>{5,6,7,1,2,4}=C

D–a–>{3,6,7,8,1,2,4}=B

D–b–>{5,6,7,1,2,4}=C

demo_DFA

状态\字符 a b
A B C
B B D
C B C
D B C

例题:将下图的DFA化为最简DFA

demo_min_DFA

例题:将下图f(i|e)e的DFA化为最简DFA

demo_DFA_1

这个算法的正则表达式是 fee 或者 fie,可以通过 fee 或 fie 到达终结状态。

N : {q0, q1, q2, q4}

A : {q3, q5}

在 N 中 q0 和 q1 在接受 e 的条件下最终得到的状态还是在 N 的内部。所以可以将其根据 e 拆分成 {q0, q1}, {q2, q4}, {q3, q5}

对于 q2 和 q4 都可以接受 e ,而且最终达到的状态一致,所以不能再进行切分。

q0 和 q1 ,在接受 e 的时候, q0 最终得到还是在 {q0, q1}这个状态的结合中, q1 却会落在 {q2, q4} 的状态中,所以可以将 q0 和 q1 分为 {q0}, {q1}。

demo_min_DFA_1

语法分析

语法分析:检查记号流的语法合法性,生成抽象语法树。【检查词法分析输出的记号流是否符合规则】

语法分析:是否符合主谓宾语法

语法分析的数学理论:上下文无关文法(CFG)

编译器的常用的语法分析方法有

  • 自顶向下分析
    • 递归下降分析算法(预测分析算法)
      • 每个非终结符构造一个分析函数
      • 用前看符号指导产生式规则的选择
    • LL分析算法
    • 动作是 推导
  • 自底向上分析
    • LR分析算法
    • 动作是 归约

文法的概念:

  • 组成语言的基本形式是 句子(句子是由 单词序列构成(单词是由 基本符号 组成))
  • 文法是阐述语法的一个工具,语句是语法的实例

推导:从非终结符推导至终结符

推导分为:

  • 最左推导
  • 最右推导(规范推导)
graph TB
    no1[<句子>]-->no2[<主语>]-->no5[<代词>]-->no8[<我>]
    no1-->no3[<谓语>]-->no6[<动词>]-->no9[<喜欢>]
    no1-->no4[<宾语>]-->no7[<名词>]-->no10[<音乐>]

归约:从终结符归约至非终结符

graph TB
    n0[<我>]-->n3[<代词>]-->n6[<主语>]-->n9[<句子>]
    n1[<喜欢>]-->n4[<动词>]-->n7[<谓语>]-->n9
    n2[<音乐>]-->n5[<名词>]-->n8[<宾语>]-->n9

上下文无关文法:

  • 上下文无关文法G是一个四元组:G=(T,N,P,S)

    • 终结符集合T
    • 非终结符积集合N
    • 产生式规则P
      • 每条规则形式:X -> β1 β2 β3 … βn
        • 其中X∈ N,βi ∈(T∪N)
    • S是唯一的开始符号(非终结符)
      • S∈N
  • 上下文无关文法例子:

    • 例子1

      • S->N,V,N
        N -> s
           | t
           | g
           | w
        V -> e
           | d
           
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
            
        - G=(N,T,P,S)

        非终结符N={S,N,V}

        终结符T={s,t,g,w,e,d}

        开始符号:S



        - 例子2

        - ```上下文无关文法
        E->num
        | id
        | E + E
        | E * E

      • G=(N,T,P,S)

        非终结符N={E}

        终结符T={num,id,+,*}

        开始符号:E

最左推导:

每次总是选择最左侧的符号进行替换

定义文法G1如下:

分析树:

  • 推导可以表达成树状结构

  • 特点:

    • 树中的每个 内部节点代表 非终结符
    • 每个叶子节点代表 终结符
    • 每一步推导代表如何从双亲节点生成它的直接孩子节点
  • E->num
     | id
     | E+E
     | E*E
     
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
      


    推导3+4*5这个句子

    - 最左推导1:

    E->E+E

    ->3+E

    ->3+E*E

    ->3+4*E

    ->3+4*5

    ![分析树1](https://gitee.com/kingwempity/images/raw/master/images/分析树1.png)

    - 最左推导2:

    E->E*E

    ->E+E*E

    ->3+E*E

    ->3+4*E

    ->3+4*5

    ![分析树2](https://gitee.com/kingwempity/images/raw/master/images/分析树2.png)



    ### **二义性文法:**

    - 给定文法G,如果存在句子S,它有两棵不同的分析树,那么称G是二义性文法

    - 解决方案:文法重写

    - ```文法重写
    E->E+T | T
    T->T*F | F
    F->num | id

    - E->E+T ->T+T ->F+T ->3+T ->3+T*F ->3+F*F ->3+4*F ->3+4*5

消除左递归:

一个文法式左递归的,如果它有非终结符A,对某个串ɑ,存在推导A==>+Aɑ.自顶而下的分析方法不能用于左递归文法,因此需要消除左递归。由A->Aɑ的产生式引起的左递归称为直接左递归。

  • 方法

    • A->Aɑ|β消除左递归

      • A->βA’
      • A’->ɑA’| ɛ
    • 例如文法

      S->Aa|b

      A->Sd| ɛ中S=>Aa=>Sda,不是直接左递归,需要化成直接左递归,

      • S->Aa|b
      • A->Aad|bd| ɛ

      最后消除左递归:

      • S->Aa|b
      • A->bdA’| ɛA’
      • A’->adA’| ɛ

提左因子:

提左因子是一种文法变换

  • 方法
    • A->αβ1 | αβ2提取左因子
      • A->αA’
      • A’->β1 | β2

FIRST集:

  • 一个文法的符号串α的开始符号集合FIRST(α):从非终结符N开始推导出的句子开头的所有可能终结符的集合,ɛ属于FIRST(α)
  • 例:
    • E->TE’
    • E’->+TE’|ɛ
    • T->FT’
    • T’->*FT’|ɛ
    • F->(E)|id
      • FIRST(E)=FIRST(TE’)=FIRST(T)=FIRST(F)={(,id}
      • FIRST(E’)={+,ɛ}
      • FIRST(T’)={*,ɛ}

FOLLOW集:

  • 非终结符A的后继符号集合FOLLOW(A)是所有句型中可以直接出现在A后面的终结符的集合,如果A是句型中最右边的非终结符,那么$属于FOLLOW(A)。
  • 如果有产生式A->αB或A->αBβ,且β=>*ɛ,那么FOLLOW(A)的一切元素都要加入FOLLOW(B)中。
  • 例:
    • E->TE’
    • E’->+TE’|ɛ
    • T->FT’
    • T’->*FT’|ɛ
    • F->(E)|id
      • FOLLOW(E)=FOLLOW(E’)={),$}
      • FOLLOW(T)=FOLLOW(T’)={+,),$}
      • FOLLOW(F)={+,*,),$}

FIRST和FOOLLOW总结:

[推荐B站视频]: https://www.bilibili.com/video/BV1Cu411m7VX/?share_source=copy_web&vd_source=89bd71848ae8204999c9aa3e48cc5694 “【3编译原理如何求first集和follow集(更正版)】”

NULLABLE集合:

  • 非终结符X属于集合NULLABLE,当且仅当:
    • 基本情况:X->
      • 归纳情况:X->Y1,Y2…Yn
        • Y1,Y2…,Yn是n个非终结符,且都属于NULLABLE集

LL(1)分析算法:

  • 从左(L)向右读入程序,最左(L)推导,采用一个(1)前看符号

  • 满足条件:A->α|β

  • 算法基本思想

    • 表驱动的分析算法
  • 0:S->N V N
    1:N->s
    2:  |t
    3:  |g
    4:  |w
    5:V->e
    6:  |d
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    - LL(1)分析表

    | N\T | s | t | g | w | e | d |
    | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
    | S | 0 | 0 | 0 | 0 | | |
    | N | 1 | 2 | 3 | 4 | | |
    | V | | | | | 5 | 6 |

    - LL(1)分析表中的冲突

    ```含冲突表
    0:S->N V N
    1:N->s
    2: |t
    3: |g
    4: |w
    5: |w V
    6:V->e
    7: |d
    - LL(1)含冲突的分析表 | N\T | s | t | g | w | e | d | | :--: | ---- | ---- | ---- | ---- | ---- | ---- | | S | 0 | 0 | 0 | 0 | | | | N | 1 | 2 | 3 | 4,5 | | | | V | | | | | 5 | 6 | - 冲突的检测: - 对N的两条产生式规则N->β和N->γ,要求FIRST(β)∩FIRST(γ)=\{\}
  • 构造预测分析表(利用FIRST集合和FOLLOW集合)

    • 对文法的每个产生式A->α

    • 步骤1:对FIRST(α)的每个终结符α,把A->α加入M[A,α]

    • 步骤2:若ɛ在FIRST(α)中,对FOLLOW(A)中每个终结符b(包含$),加入A->α加入M[A,b]

    • 例:

      • 1:E->TE’

      • 2:E’->+TE’

      • 3:E’->ɛ

      • 4:T->FT’

      • 5:T’->*FT’

      • 6:T’->ɛ

      • 7:F->(E)

      • 8:F->id

        • FIRST(E)=FIRST(TE’)=FIRST(T)=FIRST(F)={(,id}
        • FIRST(E’)={+,ɛ}
        • FIRST(T’)={*,ɛ}
        • FOLLOW(E)=FOLLOW(E’)={),$}
        • FOLLOW(T)=FOLLOW(T’)={+,),$}
        • FOLLOW(F)={+,*,),$}
      • 预测分析表

        • 预测分析表

自底向上分析算法:

  • LR分析算法(移进-归约算法)
  • 目前应用最广泛的一类语法分析器自动生成器中采用的算法
  • 自底向上分析的基本思想:最右推导的逆过程
  • 引入点记符
    • 点记符
  • 两个重要步骤:
    • 移进:一个记号到栈顶上
    • 归约:可以把归约过程看成把输入串归约成文法的开始符号
      • 文法
        • S->aABe
        • A->Abc|b
        • B->d
          • 将句子abbcde归约成S
          • 归约序列:abbcde,aAbcde,aAde,aABe,S

LL(1)文法

LL(1)文法判断

LL(1)文法判断_1

LR(0)分析表

LR(0)分析

SLR算法:

SLR分析表

LR(1)

LR(1)

例题

例题1:试着构造下列语言的文法

  • G={N,T,P,S}

    N={S}

    T={a,b}

    P={S->ɛ|ab|aSb}

  • You can't use 'macro parameter character #' in math mode{a^n#b^n|n>=0}∪{c^n#d^n|n>=0}

    G={N,T,P,S}

    N={S,X,Y}

    T={a,b,c,#,d}

    P={S->X|Y ,X->#|aXb,Y->#|cYd}

  • G={N,T,P,S}

    N={S,Y,Z}

    T={a,b,c}

    P={S->ɛ|aS|Y,Y->ɛ|bY|Z,Z->ɛ|cZ}

  • G={N,T,P,S}

    N={S,S1,S2,A,B,C,D}

    T={a,b,c}

    P={S->S1|S2,S1->AB, A->aAb|ϵ,B->cB|ϵ, S2->CD, C->bBc|ϵ, D->aD|ϵ}

  • 任何不是以0开始的所有奇整数的集合

    • 奇整数:
      奇整数的最后一位数字必须是奇数,即 {1,3,5,7,9}{1, 3, 5, 7, 9}{1,3,5,7,9}。

      非以 0 开头:
      非以 0 开头意味着数字串的第一个字符不能是 000。

      有效形式:

      • 单位数奇数:{1,3,5,7,9}。
      • 多位奇数:{d1d2…dk∣d1≠0,dk∈{1,3,5,7,9},d2,…,dk−1∈{0,1,…,9}}。
    • 非终结符

      • S: 起始符号,表示整个数字串。
      • D: 表示第一个非零数字(开头数字)。
      • U: 表示单位数奇数(单个位数的奇整数)
      • M: 表示中间可以是任意数字的部分(包含 0 到 9)。
      • O: 表示奇数结尾。
    • 文法规则

      S→DO∣DMO

      U→O

      D→1∣2∣3∣4∣5∣6∣7∣8∣9

      M→0M∣1M∣2M∣3M∣4M∣5M∣6M∣7M∣8M∣9M∣ϵ

      O→1∣3∣5∣7∣9

    G={N,T,P,S}

    N={S,C,M,U,O}

    P={S→U∣DMO

    U->O

    D→1∣2∣3∣4∣5∣6∣7∣8∣9

    M→0M∣1M∣2M∣3M∣4M∣5M∣6M∣7M∣8M∣9M∣ϵ

    O→1∣3∣5∣7∣9}

例题2: 考虑文法

(a) 为句子 abab 构造两个不同的最左推导,以此说明该文法是二义的。
(b) 为 abab 构造对应的最右推导。
(c) 为 abab 构造对应的分析树。
(d) 这个文法产生的语言是什么?

(a) S->aSbS S->aSbS
->abS ->abSaSbS
->abaSbS ->abaSbS
->ababS ->ababS
->abab ->abab

可知改文法是二义的

(b)S->aSbS S->aSbS
->aSb ->aSbaSbS
->abSaSb ->aSbaSb
->abSab ->aSbab
->abab ->abab

(c)

二义分析树

(d)产生a,b数量相等的ab串。

例题3:文法
$$R\to R’|’R\ \ |\ \ RR\ \ |\ \ R\ |\ (R)\ |\ a\ |\ b$$
产生字母表{a,b}上所有不含 ε的正规式。注意,第一条竖线是正规式的符号“或”,而
不是文法产生式右部各选择之间的分隔符,另外“*”在这儿是一个普通的终结符。该文
法是二义的。
(a) 为该文法写一个等价的非二义的文法。运算优先级:星闭包 (∗) > 连接 (RR) > 或 (∣)。
(b) 按上面两个文法构造句子 ab|b
a 的分析树。

(a)

S->E
E->T|E’
E’->E’T
T->FT’
T’->T|ɛ
F->(E)|F*|a|b

(b)

学不动了,后面再学吧,救救孩子吧

例题4:选择题

  • 下列关于LL(1)分析表的说法中正确的是(A)
    • A.LL(1)分析表指示栈顶为非终结符时面临输入符号应该选择的产生式。
    • B.LL(1)文法的LL(1)分析表可以有多重定义。
    • C.一个产生式只能在LL(1)分析表中填写一次。
    • D.LL(1)分析表也可用于自底向上语法分析。

例题5:

求下面文法每个非终结符号的FIRST集和FOLLOW集,并构造下面文法的LL(1)分析表。

  • S:=aAd
  • A:=BC
  • B:=b|ɛ
  • C:=c|ɛ

FIRST(S)={a} |FOLLOW(S)={$}

FIRRST(A)={b,c,ɛ} |FOLLOW(A)={d}

FIRRST(B)={b,ɛ} |FOLLOW(B)={c,d}

FIRRST(C)={c,ɛ} |FOLLOW(C)={d}

LL(1)分析表

a b c d $
S 1
A 2 2 2
B 3 3 4
C 5 6

语义分析

语义分析:检查抽象语法树的合法性

语义分析:类型检查、变量声明检查、参数类型检查、参数数量检查、函数是否声明、函数是否定义

语法制导翻译

精简词法分析实现实验

一、实验目的

1、 深入理解有限自动机及其应用

2、 编辑一个词法分析器,了解计算机识别源程序字符串的过程。

二、实验内容和要求

实验内容:处理c语言源程序,对源程序进行编译预处理(去除注释、无用的回车换行找到包含的文件等)之后,对整个源程序进行分解,分解成一个个单词,这些单词有且只有五类,分别是标识符、保留字、常数、运算符、界符。

实验要求:编写一个简单的词法分析程序,

输入:源程序,

输出:二元组<单词本身,种别码>。

程序流程:

1、 打开源程序,读取文件内容,直至遇到“$”文件结束符,读取结束。

2、 对读取的文件进行预处理,从头到尾进行扫描,去除//和/* ***/的内容,以及一些无用的、影响程序执行的符号如换行符、回车符、制表符等**。(但是千万注意不要在这个时候去除空格)。

3、 对源文件从头到尾进行扫描,扫描前判断当前字符是不是空格,如果是,扫描下一个字符,直至不是空格,然后判断是不是字母,若是则进行标识符和保留字的识别;若这个字符为数字,则进行数字的判断。否则,依次对这个字符可能的情况进行判断,若是将所有可能都走了一遍还是没有知道它是谁,则****认定为错误符号,输出该错误符号,然后结束。每次成功识别了一个单词后,单词都会存在token[ ]中。然后确定这个单词的种别码,最后进行下一个单词的识别。(简单起见,数字只是整数。)

4、 主控程序主要负责对每次识别的种别码syn进行判断,对于不同的单词种别做出不同的反应,如对于标识符则将其插入标识符表中。对于保留字则输出该保留字的种别码和助记符,等等。**直至遇到syn=0;**程序结束。

备注:需要将识别的源代码放入.\source.txt文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <cctype>
using namespace std;

// 保留字表
const unordered_map<string, int> reserveWords = {
{"main",0}, {"auto", 1}, {"break", 2}, {"case", 3}, {"char", 4}, {"const", 5}, {"continue", 6},
{"default",7},{"do",8},{"double",9},{"else",10}, {"enum",11},{"extern",12},{"float",13},{"for",14},
{"goto",15},{"if",16},{"int",17},{"long",18},{"register",19},{"return",20},{"short",21},
{"signed",22},{"sizeof",23},{"static",24},{"struct",25},{"switch",26},{"typedef",27},
{"union",28},{"unsigned",29},{"void",30},{"volatile", 31}, {"while", 32}
};

// 运算符和界符表
const unordered_map<string, int> operatorsAndDelimiters = {
{"(", 17}, {")", 18}, {"{", 19}, {"}", 20}, {"[", 21}, {"]", 22},
{"<", 23}, {"<=", 24}, {">", 25}, {">=", 26}, {"=", 27}, {"==", 28},
{"+", 29}, {"-", 30}, {"*", 31}, {"/", 32}, {"^", 33}, {";", 34}
};

// 标识符表
vector<string> identifiers;

// 辅助函数
bool isLetter(char ch) { return isalpha(ch) || ch == '_'; }
bool isDigit(char ch) { return isdigit(ch); }

// 查找保留字
int searchReserve(const string& s) {
auto it = reserveWords.find(s);
if (it != reserveWords.end()) return it->second;
return -1; // 不是保留字
}

// 输出二元组
void outputToken(const string& token, int typeCode) {
cout << "<" << token << "," << typeCode << ">" << endl;
}

// 编译预处理
string preprocess(const string& source) {
string result;
bool inComment = false;
for (size_t i = 0; i < source.size(); ++i) {
char ch = source[i];
if (ch == '/' && i + 1 < source.size() && source[i + 1] == '/') { // 单行注释
while (++i < source.size() && source[i] != '\n');
}
else if (ch == '/' && i + 1 < source.size() && source[i + 1] == '*') { // 开始多行注释
inComment = true;
++i;
}
else if (inComment && i + 1 < source.size() && source[i] == '*' && source[i + 1] == '/') { // 结束多行注释
inComment = false;
++i; // 跳过 '/'
}
else if (!inComment && (!isspace(ch) || ch == ' ')) { // 只添加非注释部分的非空白字符或空格
result += ch;
}
}
return result;
}

// 扫描源文件
void scanner(const string& source) {
string token;
for (size_t i = 0; i < source.size(); ++i) {
char ch = source[i];
if (isspace(ch)) continue; // 忽略空白

if (isLetter(ch) || ch == '_') {
token = ch;
while (i + 1 < source.size() && (isLetter(source[i + 1]) || isDigit(source[i + 1]) || source[i + 1] == '_')) {
token += source[++i];
}
int code = searchReserve(token);
if (code == -1) { // 标识符
identifiers.push_back(token);
outputToken(token, 100);
}
else { // 保留字
outputToken(token, code);
}
}
else if (isDigit(ch)) {//常数
token = ch;
while (i + 1 < source.size() && isDigit(source[i + 1])) {
token += source[++i];
}
outputToken(token, 99);
}
else {
// 尝试识别多字符运算符
string op(1, ch);
if (i + 1 < source.size()) {
op += source[i + 1];
auto it = operatorsAndDelimiters.find(op);
if (it != operatorsAndDelimiters.end()) {
outputToken(op, it->second);
++i; // 跳过下一个字符
continue;
}
}

// 如果不是多字符运算符,则尝试单字符运算符或界符
if (operatorsAndDelimiters.count(string(1, ch))) {
outputToken(string(1, ch), operatorsAndDelimiters.at(string(1, ch)));
}
else {
if (ch == '$') break; // 结束符
cout << "Unknown symbol: " << ch << " at position " << i << endl;
}
}
}
}

// 打印源代码并返回文件内容
string prReContent(const string& filename) {
ifstream file(filename);
if (!file.is_open()) {
cerr << "Failed to open file." << endl;
return "";
}

cout << "=== Source Code =====\n";
string line, source;
while (getline(file, line)) {
cout << line << endl;
source += line + '\n'; // 确保保留行之间的换行符
}
cout << "=====================\n\n";

file.close();
return source;
}

int main() {
const string filename = "source.txt";

// 打印文件源代码并获取其内容
string source = prReContent(filename);
if (source.empty()) {
// 如果文件打开失败,直接退出
return 1;
}

string processedSource = preprocess(source);
scanner(processedSource);

return 0;
}

LUM图

graph TB
    A[开始] --> B[打印源代码]
    B -->|调用printSource|C[打开文件]
    C -->|失败|D[/输出错误信息并结束/]
    C -->|成功|E[打印文件内容]
    E -->I[读取文件内容]
    I --> J[关闭文件]
    J --> K[预处理源代码]
    K -->|调用preprocess|L[遍历字符]
    L --> M[返回预处理后的源代码]
    M --> N[扫描源代码]
    N -->|调用scanner|O[初始化变量]
    O --> P[遍历字符]
    P -->|忽略空白字符|Q[处理标识符/保留字]
    Q -->|处理数字|R[处理运算符和界符]
    R -->|遇到$符号|S[结束扫描]
    S --> T[结束]
    P -->|结束|S
    R -->|遇到未知符号|U[/输出错误信息/]
    U --> P
  • Title: 编译原理
  • Author: 敬请T期待
  • Created at : 2024-11-21 15:02:31
  • Updated at : 2025-02-20 20:02:42
  • Link: https://kingwempity.github.io/2024/11/21/编译原理/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments