自己动手写编译器:增强语法极其实现

我们前面章节看到的语法规则中,语法只给出了代码字符串组合规则是否符合规定,实际上我们可以在语法解析过程中增加一些特定的属性或者操作,使得语法解析流程中就能完成中间代码生成,或者是创建好特定的元信息,以便在后续处理流程中辅助代码生成。例如我们看看如何在语法解析规则中附加特定操作,使得语法解析过程就能生成中间代码,我们看一个例子,给定如下语法规则:

expr_prime -> + term {op('+');} expr_prime

其中{op(‘+’)}就是对语法的增强,它表示在解析完 term 这个符号后,执行 op(‘+’)这个操作,对应到代码实现上就如下所示:

expr_prime() {
    if (match(PLUS)) {
        term()
        op('+')
        expr_prime()
    }
}

要想理解增强语法的特性,我们还是需要去实现一个具体实例,我们现给出一个能解析算术表达式的增强语法规则:

stmt -> epsilon | expr ; stmt
expr -> term expr_prime 
expr_prime -> + term {op('+')} expr_prime | epsilon 
term -> factor term_prime 
term_prime -> * factor {op('*')} term_prime
factor -> NUM {create_tmp(lexer.lexeme)} | ( expr )

上面的语法用于识别加法,乘法,以及带括号的算术表达式,他跟我们前面用于识别表达式的语法有所不同,它这里主要是进行了“左递归消除”,在后续章节我们会详细讨论这个话题,那么我们怎么用上面语法来解析表达式呢,解析完毕后会有什么效果呢,我们看具体实现你就会明白了。首先在前dragon-compiler 项目中创建一个文件夹叫augmented_parser,在该目录下创建新文件叫 augmented_parser.go,添加代码如下:

package augmented_parser

import (
    "fmt"
    "lexer"
)

type AugmentedParser struct {
    parserLexer lexer.Lexer
    //用于存储虚拟寄存器的名字
    registerNames []string
    //存储当前已分配寄存器的名字
    regiserStack []string
    //当前可用寄存器名字的下标
    registerNameIdx int
    //存储读取后又放回去的 token
    reverseToken []lexer.Token
}

func NewAugmentedParser(parserLexer lexer.Lexer) *AugmentedParser {
    return &AugmentedParser{
        parserLexer:     parserLexer,
        registerNames:   []string{"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"},
        regiserStack:    make([]string, 0),
        registerNameIdx: 0,
        reverseToken:    make([]lexer.Token, 0),
    }
}

func (a *AugmentedParser) putbackToken(token lexer.Token) {
    a.reverseToken = append(a.reverseToken, token)
}

func (a *AugmentedParser) newName() string {
    //返回一个寄存器的名字
    if a.registerNameIdx >= len(a.registerNames) {
        //没有寄存器可用
        panic("register name running out")
    }
    name := a.registerNames[a.registerNameIdx]
    a.registerNameIdx += 1
    return name
}

func (a *AugmentedParser) freeName(name string) {
    //释放当前寄存器名字
    if a.registerNameIdx > len(a.registerNames) {
        panic("register name index out of bound")
    }

    if a.registerNameIdx == 0 {
        panic("register name is full")
    }

    a.registerNameIdx -= 1
    a.registerNames[a.registerNameIdx] = name
}

func (a *AugmentedParser) createTmp(str string) {
    //创建一条寄存器赋值指令并
    name := a.newName()
    //生成一条寄存器赋值指令
    fmt.Printf("%s=%s\n", name, str)
    //将当前使用的寄存器压入堆栈
    a.regiserStack = append(a.regiserStack, name)
}

func (a *AugmentedParser) op(what string) {
    /*
        将寄存器堆栈顶部两个寄存器取出,生成一条计算指令,
        并赋值给第二个寄存器,然后释放第一个寄存器,第二个寄存器依然保持在堆栈上
    */
    right := a.regiserStack[len(a.regiserStack)-1]
    a.regiserStack = a.regiserStack[0 : len(a.regiserStack)-1]
    left := a.regiserStack[len(a.regiserStack)-1]
    fmt.Printf("%s %s= %s\n", left, what, right)
    a.freeName(right)
}

func (a *AugmentedParser) getToken() lexer.Token {
    //先看看有没有上次退回去的 token
    if len(a.reverseToken) > 0 {
        token := a.reverseToken[len(a.reverseToken)-1]
        a.reverseToken = a.reverseToken[0 : len(a.reverseToken)-1]
        return token
    }

    token, err := a.parserLexer.Scan()
    if err != nil && token.Tag != lexer.EOF {
        sErr := fmt.Sprintf("get token with err:%s\n", err)
        panic(sErr)
    }

    return token
}

func (a *AugmentedParser) match(tag lexer.Tag) bool {
    token := a.getToken()
    if token.Tag != tag {
        a.putbackToken(token)
        return false
    }

    return true
}

func (a *AugmentedParser) Parse() {
    a.stmt()
}

func (a *AugmentedParser) isEOF() bool {
    token := a.getToken()
    if token.Tag == lexer.EOF {
        return true
    } else {
        a.putbackToken(token)
    }
    return false
}

func (a *AugmentedParser) stmt() {
    //stmt-> epsilon
    if a.isEOF() {
        return
    }
    //stmt -> expr ; stmt
    a.expr()
    if a.match(lexer.SEMI) != true {
        panic("mismatch token, expect semi")
    }
    a.stmt()
}

func (a *AugmentedParser) expr() {
    //expr -> term expr_prime
    a.term()
    a.expr_prime()
}

func (a *AugmentedParser) expr_prime() {
    //expr_prime -> + term {op('+')} expr_prime
    if a.match(lexer.PLUS) == true {
        a.term()
        a.op("+")
        a.expr_prime()
    }

    //expr -> epsilon
    return
}

func (a *AugmentedParser) term() {
    //term -> factor term_prime
    a.factor()
    a.term_prime()
}

func (a *AugmentedParser) term_prime() {
    //term_prime -> * factor {op('*')} term_prime
    if a.match(lexer.MUL) == true {
        a.factor()
        a.op("*")
        a.term_prime()
    }
    //term_prime -> epsilon
    return
}

func (a *AugmentedParser) factor() {
    // factor -> NUM {create_tmp(lexer.lexeme)}
    if a.match(lexer.NUM) == true {
        a.createTmp(a.parserLexer.Lexeme)
        return
    } else if a.match(lexer.LEFT_BRACKET) == true {
        a.expr()
        if a.match(lexer.RIGHT_BRACKET) != true {
            panic("mismatch token, expect right_paren")
        }
        return
    }

    //should not come here
    panic("factor parsing error")
}

在代码实现中有几处需要留意,一是代码存储了多个虚拟寄存器的名称,在读取表达式时,一旦读取到数字字符,那么就会将其数值赋值给某个寄存器,例如“1+2”,当代码读取字符1 时就会取出寄存器 t0,然后生成语句 t0=1,这个功能是由 createTmp 函数实现,调用该接口时输入的参数就对应当前读取到的数字。

在前面的语法规则中有{op(‘+’)}这样的指令,它在代码中对应函数 op,该函数从当前指令堆栈中取出顶部两个寄存处,然后执行加法指令,假设当前栈顶两个寄存器是 t0,t1,那么 op(‘+’)执行后就会创建指令 t1+=t0,然后它会把 t0 从堆栈去除,但是会保留 t1 在堆栈顶部。

在 main.go 中调用上面实现的代码测试一下效果:

package main

import (
    "augmented_parser"
    "lexer"
)

func main() {
    exprLexer := lexer.NewLexer("1+2*(4+3);")
    augmentedParser := augmented_parser.NewAugmentedParser(exprLexer)
    augmentedParser.Parse()
}

上面代码执行后所得结果如下:

t0=1
t1=2
t2=4
t3=3
t2 += t3
t1 *= t2
t0 += t1

可以看成生成的虚拟指令确实能对应得上给定的算术表达式,更详细的调试演示过程请在 b 站搜索 coding 迪斯尼。代码下载:

https://github.com/wycl16514/augmented-grammar.git

<think>嗯,用户想学习如何编写编译器,特别是自制编译器的教程、编程和实现方法。首先,我需要回忆一下关于编译器开发的基本知识,以及之前用户提供的引用内容。用户提供的引用资料里提到了几个关键点,比如编译器的步骤、中间代码生成、汇编代码生成规则,以及具体的工具如GNU as和javacc。 首先,我应该整理出编译器开发的基本步骤,用户可能对整体流程不太清楚,所以需要分阶段解释。预处理、词法分析、语法分析、语义分析、中间代码生成、优化、目标代码生成和链接这些步骤都需要简要说明。然后,结合引用中的例子,比如Cb编译器实现,可以给出具体的例子,比如用Lex和Yacc工具,或者JavaCC,这样用户有实际的方向。 接下来,用户可能需要知道学习路径。从基础开始,编译原理的理论知识是必须的,比如龙书。然后实践部分,选择简单的语言子集,逐步扩展。引用中的资料提到C语言的子集Cb,这可能是一个好的起点。同时,工具链的选择也很重要,比如Flex和Bison,或者JavaCC,这些工具能帮助简化词法分析和语法分析的过程。 然后,用户可能想知道具体的实现步骤,比如如何管理符号表,类型检查,生成中间代码,以及如何转换成汇编。引用3提到了生成汇编代码的三个规则,这些具体的实现细节对用户来说可能很有帮助,可以举例说明。例如,变量存储在内存中,使用mov指令操作内存,以及寄存器的使用限制。 另外,用户可能对实际的项目结构或代码示例感兴趣。引用4给出了一个简单的编译命令流程,从编译到生成可执行文件,这可以作为一个简单的例子。可能需要建议用户从类似的小项目开始,逐步增加功能。 用户的问题中还提到自制编译器的教程,所以需要推荐一些资源,比如《自制编译器》这本书,以及在线资料如Stanford的课程。同时,实践建议部分,分阶段开发、使用测试用例、参与开源项目等,都是有用的建议。 最后,生成相关问题部分要考虑用户可能遇到的后续问题,比如编译器各阶段的具体实现、工具选择、处理复杂语法、优化技术,以及调试方法。这些问题能引导用户深入学习。 需要注意的是,用户提供的引用中有几个关于Cb编译器实现细节,比如符号表管理、类型检查、代码生成,这些都应该在回答中提及,以增强回答的可信度和相关性。同时,确保按照系统指令正确使用LaTeX格式,例如在提到中间代码时使用$...$,或者独立公式用$$...$$,但在这个回答中可能公式不多,主要注意代码块和引用标记的正确使用。 总结来说,回答的结构应该是先概述编译器开发步骤,然后分阶段详细说明,结合引用中的实例,接着给出学习路径和推荐资源,最后提供相关问题。确保每个部分都自然地引用提供的参考文献,帮助用户构建系统的知识框架。</think>### 自制编译器实现教程 #### 1. 编译器开发基础步骤 编译器实现通常分为以下阶段: - **词法分析**:将源代码转换为词法单元(Token),例如识别变量名、运算符等。工具如Lex/Flex可自动化此过程[^2][^3]。 - **语法分析**:构建抽象语法树(AST),验证代码结构是否符合语法规则。Yacc/Bison或JavaCC是常用工具[^3]。 - **语义分析**:检查类型一致性、变量声明等,生成符号表[^1][^5]。 - **中间代码生成**:生成平台无关的中间表示(如三地址码、LLVM IR),便于后续优化和移植。 - **代码优化**:对中间代码进行性能改进(如常量折叠、循环优化)。 - **目标代码生成**:将中间代码转换为目标机器码(如x86汇编),需处理寄存器分配和指令选择[^4]。 - **链接与加载**:合并库函数和生成可执行文件[^4]。 #### 2. 实践步骤与工具链 **示例:C语言子集编译器(Cb)实现** ```c // 示例:解析简单表达式 int main() { return 2 * (3 + 4); } ``` - **词法分析**:识别`int`, `main`, `(`, `)`, `{`, `return`, `2`, `*`, `3`, `+`, `4`等Token。 - **语法分析**:构建AST,验证函数定义和表达式结构。 - **汇编生成**:遵循规则生成x86汇编代码: ```asm mov eax, 3 add eax, 4 imul eax, 2 ret ``` **工具推荐**: - **Lex/Yacc**:传统词法/语法分析工具 - **JavaCC**:适合生成递归下降解析器 - **LLVM**:用于中间代码优化和生成 #### 3. 学习路径建议 1. **理论基础**:学习《编译原理》(龙书)中的正则表达式、上下文无关文法、语法制导翻译。 2. **实践入门**:从四则运算表达式解析开始,逐步支持变量、函数、控制流。 3. **代码生成**:研究栈帧布局、寄存器分配策略(如引用3的“虚拟栈分配临时变量”规则)。 4. **调试技巧**:通过`-S`输出汇编代码验证结果。 #### 4. 推荐资源 - 书籍:《自制编译器》(详细讲解汇编生成和链接) - 在线课程:Stanford CS143《编译原理》 - 开源项目:TCC(小型C编译器)、LLVM教程 #### 5. 关键挑战与解决方案 - **符号表管理**:需记录变量类型和作用域,如引用1所述。 - **类型检查**:需实现隐式转换规则(如int→float)。 - **优化策略**:中间代码层应用常量传播、死代码删除。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值