如何使用Python开发自己的编译器

1. 前言

总所周知,编译器是一个将一种语言(源语言)翻译成另一种语言(目标语言)的程序,如果我们只想使用它,我们只需要将它看作一个黑盒子即可不必关心它的实现,如图1所示。
图1  编译器

图1 编译器

但是如果你想发明一种新的语言,你就需要了解它的内部构造了,因为要发明一门新语言,其实你需要做的就是编写一个新的编译器。实际上,编译器将源程序翻译成目标程序的过程可以分为词法分析、语法分析、语义分析以及目标代码生成等多个阶段,如图2所示。通常,我们称词法分析、语法分析、语义分析以及中间代码生成这几个阶段为前端,而代码优化以及目标代码生成为后端。根据使用场景的不同,其中有些阶段不是必须的,例如一个编译器可以没有中间代码以及代码优化。但是即便一个只包含词法分析、语法分析语义分析的简单编译器,如果需要从零开始也是比较困难的,需要非常熟悉编译原理。
图2 编译器编译过程

图2 编译器编译过程

由于编译原理太过复杂,为了能让开发一款编译器变得更高效,出现了很多编译器框架,例如著名的LLVM。在之前的文章LLVM,一堆积木的故事中介绍过,LLVM提供了所有编译器所需的组件,我们只需要增加或者替换一些特定组件,就能实现一个新的编译器。例如,只需要提供一个新的前端,你就能实现一个运行在目前LLVM所支持的硬件的上的全新语言。

那么要怎么快速的实现自己的前端呢?这就是我们今天的主角——PLY——所做的事情。

2. 铺垫

2.1. 词法分析器

词法分析的作用是将组成源程序的字符流识别成一个一个的记号(Token),并去除多余的空格以及注释等,方便语法分析器进行后续的语法分析,其工作原理如图3所示。
图3 词法分析工作原理

图2 编译器编译过程

例如在C语言中,词法分析器会将int value = 100;这个表达式转变为下列的一个个记号:

int (keyword), value (identifier), = (operator), 100 (constant) and ; (symbol)
2.2. 语法分析器

语法分析器——也叫解析器——的作用就是将从词法分析器获得的记号流与给定的一条条规则进行比对,从而检测源程序中是否存在错误,这些规则称为产生式(Production)。如果源程序没有错误,词法分析器会输出一个解析树,也成为抽象语法树(AST)。语法分析的工作原理如图4所示。
图4 语法分析器工作原理

图2 编译器编译过程
2.3. BNF

既然词法分析器是通过产生式来判断源代码是否有错误,那么我们就得先知道产生式是什么东西。我们知道,每一种程序设计语言都有其描述语法规则的结构,而这些描述语法的结构就可以用上下文无关文法——也就是BNF范式——来描述。

BNF是由John Backus以及Peter Baur提出的,它可以用于描述上下文无关语言,例如可以用于描述以一个语言中的加减乘除操作,其形态如图5所示。组成BNF范式的每一条规则就是一个产生式。

图3 BNF示例

图3 BNF示例

如上图BNF范式所示,其描述了某种语言中的加法和乘法操作,例如在算数表达式24 * 43中,经过词法分析会得到24*43这三个记号,其中2443都是id。我们首先通过第三条产生式将2443都替换成了E,得到了E * E,之后,我们发现产生式2正好可以匹配,说明算数表达式24 * 43是没有语法错误的。反之,由于这个BNF范式中没有定义减法的产生式,因此对于算数表达式88 - 43,最终找不到与它想匹配的产生式,因此就会出现语法错误。

2.4.Lex & Yacc

Lex 与 Yacc是用于构建编译器前端的两个工具,他们分别由Eric Schmidt 与Stephen Johnson于上世纪70年代创造。Lex用于词法分析,而Yacc(Yet Another Compiler Compiler)用于语法分析,后续的许多解析器都是他们的变种。
而今天介绍的PLY,就是Lex以及Yacc的纯Python实现

2.5. PLY简介

PLY,全称为Python Lex-Yacc,是Lex以及Yacc的纯Python实现,用于构建编译器的前端,Lex负责词法分析,Yacc负责语法分析。他们拥有与传统的Lex\Yacc一样的功能。PLY这个库的结构很简单,就包含两个重要文件lex.py 以及yacc.py。使用的时候只需要在你的工程下新建一个目录并命名为ply然后将这两个文件拷贝进去,然后通过import ply.lex以及import ply.yacc这两个语句导入就可以使用了。

3. PLY举例

我们使用PLY的时候需要遵守一定的规则,根据需要定义一些我们需要的变量以及函数。PLY运行的时候会通过自省的方式获取到我们定义的变量以及参数用于进行词法分析以及语法分析。

3.1. Lex

首先我们来看看如果我们要使用Lex我们需要做些什么,我们将以下面的代码为例子作为讲解。

 import ply.lex as lex
  # List of token names.   This is always required
 tokens = (
    'NUMBER',
    'PLUS',
    'MINUS',
    'TIMES',
    'DIVIDE',
    'LPAREN',
    'RPAREN',
 )
 
 # Regular expression rules for simple tokens
 t_PLUS    = r'\+'
 t_MINUS   = r'-'
 t_TIMES   = r'\*'
 t_DIVIDE  = r'/'
 t_LPAREN  = r'\('
 t_RPAREN  = r'\)'
 
 # A regular expression rule with some action code
 def t_NUMBER(t):
     r'\d+'
     t.value = int(t.value)    
     return t
 
 # Define a rule so we can track line numbers
 def t_newline(t):
     r'\n+'
     t.lexer.lineno += len(t.value)
 
 # A string containing ignored characters (spaces and tabs)
 t_ignore  = ' \t'
 
 # Error handling rule
 def t_error(t):
     print("Illegal character '%s'" % t.value[0
  • 6
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值