3.2.5.9 写一个词法分析器

词法分析器或者叫扫描器主要用来分析字符串的文本,然后把文本里组成的单词分析出来,识别为某一类型的属性。对于编写编译器或者解析器的第一步工作就是做这样的事情:词法分析。以前有很多种使用字符串搜索的办法,这里使用正则表达式来实现这个目的。

例子:

print("词法分析器")

import collections
import re

Token = collections.namedtuple(‘Token‘, [‘typ‘, ‘value‘, ‘line‘, ‘column‘])

def tokenize(code):
    keywords = {‘IF‘, ‘THEN‘, ‘ENDIF‘, ‘FOR‘, ‘NEXT‘, ‘GOSUB‘, ‘RETURN‘}
    token_specification = [
        (‘NUMBER‘,  r‘\d+(\.\d*)?‘), # Integer or decimal number
        (‘ASSIGN‘,  r‘:=‘),          # Assignment operator
        (‘END‘,     r‘;‘),           # Statement terminator
        (‘ID‘,      r‘[A-Za-z]+‘),   # Identifiers
        (‘OP‘,      r‘[+\-*/]‘),     # Arithmetic operators
        (‘NEWLINE‘, r‘\n‘),          # Line endings
        (‘SKIP‘,    r‘[ \t]+‘),      # Skip over spaces and tabs
        (‘MISMATCH‘,r‘.‘),           # Any other character
    ]
    tok_regex = ‘|‘.join(‘(?P<%s>%s)‘ % pair for pair in token_specification)
    line_num = 1
    line_start = 0
    for mo in re.finditer(tok_regex, code):
        kind = mo.lastgroup
        value = mo.group(kind)
        if kind == ‘NEWLINE‘:
            line_start = mo.end()
            line_num += 1
        elif kind == ‘SKIP‘:
            pass
        elif kind == ‘MISMATCH‘:
            raise RuntimeError(‘%r unexpected on line %d‘ % (value, line_num))
        else:
            if kind == ‘ID‘ and value in keywords:
                kind = value
            column = mo.start() - line_start
            yield Token(kind, value, line_num, column)

statements = ‘‘‘
    IF quantity THEN
        total := total + price * quantity;
        tax := price * 0.05;
    ENDIF;
‘‘‘

for token in tokenize(statements):
    print(token)

结果输出如下:

词法分析器

Token(typ=‘IF‘, value=‘IF‘, line=2, column=4)

Token(typ=‘ID‘, value=‘quantity‘, line=2, column=7)

Token(typ=‘THEN‘, value=‘THEN‘, line=2, column=16)

Token(typ=‘ID‘, value=‘total‘, line=3, column=8)

Token(typ=‘ASSIGN‘, value=‘:=‘, line=3, column=14)

Token(typ=‘ID‘, value=‘total‘, line=3, column=17)

Token(typ=‘OP‘, value=‘+‘, line=3, column=23)

Token(typ=‘ID‘, value=‘price‘, line=3, column=25)

Token(typ=‘OP‘, value=‘*‘, line=3, column=31)

Token(typ=‘ID‘, value=‘quantity‘, line=3, column=33)

Token(typ=‘END‘, value=‘;‘, line=3, column=41)

Token(typ=‘ID‘, value=‘tax‘, line=4, column=8)

Token(typ=‘ASSIGN‘, value=‘:=‘, line=4, column=12)

Token(typ=‘ID‘, value=‘price‘, line=4, column=15)

Token(typ=‘OP‘, value=‘*‘, line=4, column=21)

Token(typ=‘NUMBER‘, value=‘0.05‘, line=4, column=23)

Token(typ=‘END‘, value=‘;‘, line=4, column=27)

Token(typ=‘ENDIF‘, value=‘ENDIF‘, line=5, column=4)

Token(typ=‘END‘, value=‘;‘, line=5, column=9)

在这个例子里,先从库collections导入namedtuple,以便可以记录每个单词(Token)的属性,在这里主要为4个属性:类型、值、行号、列号,因此创建一个命名的元组Token数据结构,为后面保存每一个单词属性提供了空间。

接着定义一个函数tokenize(),在函数里先定义关键字集合keywords,定义识别不同单词的正则表达式的字典token_specification,然后通过通过字符串join函数生成一个正则表达式:

(?P<NUMBER>\d+(\.\d*)?)|(?P<ASSIGN>:=)|(?P<END>;)|(?P<ID>[A-Za-z]+)|(?P<OP>[+\-*/])|(?P<NEWLINE>\n)|(?P<SKIP>[ \t]+)|(?P<MISMATCH>.)

通过上面正则表达式,就可匹配所有规则,只要匹配成功,就保存在最后一个分组里,因而使用了lastgroup来获取。具体来分析一个正则表达式(?P<NUMBER>\d+(\.\d*)?),外面大括号表示分组,?表示可以出现,P<NUMBER>表示分组的名称为NUMBER,\d+表示匹配所有数字字符,+(\.\d*)?表示小数点部分是否可以存在。

通过语句re.finditer(tok_regex, code)来匹配整个输入文本中所有单词,然后在for循环里得到所有匹配成功的单词,接着判断单词的类型,并根据换行符来增加行号,判断列号,有了单词的类型、行号、列表号,再把值保存起来就完成了整个词法分析过程。

值得注意的是,由于分析的文本长度不限,有可能达到1M,或者几M大小,因此不可能把所有单词全部保存在一起再返回,因而采用迭代子的方式进行返回,从而使用了关键字yield来返回。从上面看来,有了正则表达式的功能,再来写一个词法分析器是轻而易举的事情,不费吹灰之力,这是为“懒人”准备的好工具。

蔡军生  微信号:shenzhencai  深圳

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-07 12:27:08

3.2.5.9 写一个词法分析器的相关文章

手写一个词法分析器

前言 最近大部分时间都在撸 Python,其中也会涉及到将数据库表转换为 Python 中 ORM 框架的 Model,但我们并没有找到一个合适的工具来做这个意义不大的"体力活",所以每次新建表后大家都是根据自己的表结构手写一遍 Model. 一两张表还好,一旦 10 几张表都要写一遍时那痛苦只有自己知道:这时程序员的 slogan 再次印证:一切毫无意义的体力劳动终将被计算机取代. intellij plugin 既然没有现成的工具那就自己写一个吧,演示效果如下: 考虑到我们主要是用

如何写一个解释器(1):编译原理

最近在看DSL的东西,对于外部DSL,写一个解释器是必不可少的.我试图归纳一下我学到的,以写一个解释器为目标,讲一下如果来实现一个可用的解释器.一个解释器通常可以分为一下几个阶段: 词法分析(Lexer) 语法分析(Parser, BNF, CFG, AST) 语义分析(AST的处理, annotated AST) 目标语言生成(stack-based) 这里的解释器不包括目标语言的执行和运行时环境,如果需要类似于python/ruby的解析执行器的话,还需要bytecode-compiler,

一起写一个JSON解析器

[本篇博文会介绍JSON解析的原理与实现,并一步一步写出来一个简单但实用的JSON解析器,项目地址:SimpleJSON.希望通过这篇博文,能让我们以后与JSON打交道时更加得心应手.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指正:)] 一.JSON解析器介绍 相信大家在平时的开发中没少与JSON打交道,那么我们平常使用的一些JSON解析库都为我们做了哪些工作呢?这里我们以知乎日报API返回的JSON数据来介绍一下两个主流JSON解析库的用法.我们对地址 http://

【转】写一个C语言编译器 : BabyC

[转载]此文是转载,方便以后读与学习. 原文链接:http://blog.jobbole.com/77305/ 动手编写一个编译器,学习一下较为底层的编程方式,是一种学习计算机到底是如何工作的非常有效方法. 编译器通常被看作是十分复杂的工程.事实上,编写一个产品级的编译器也确实是一个庞大的任务.但是写一个小巧可用的编译器却不是这么困难. 秘诀就是首先去找到一个最小的可用工程,然后把你想要的特性添加进去.这个方法也是Abdulaziz Ghuloum在他那篇著名的论文“一种构造编译器的捷径”里所提

请写一个算法,用于将list集合内重复元素剔除

package Homework; import java.util.ArrayList;import java.util.Iterator;import java.util.List;import java.util.Scanner;/** * list集合是否可以包含重复元素? * 如果可以,请写一个算法,用于将list集合内重复元素剔除. * @author 张致远 * */public class Homework2 { public static void main(String[]

用java写一个远程视频监控系统,实时监控(类似直播)我想用RPT协议,不知道怎么把RPT协议集成到项目中

我最近在用java写一个远程视频监控系统,实时监控(类似直播)我想用RPT协议,不知道怎么把RPT协议集成到项目中,第一次写项目,写过这类项目的多多提意见,哪方面的意见都行,有代码或者demo的求赏给我,谢谢

c语言:写一个函数建立一个有3名学生数据的单向动态链表

写一个函数建立一个有3名学生数据的单向动态链表. 解:程序: #include<stdio.h> #include<stdlib.h> #define LEN sizeof(struct Student) struct Student { long num; float score; struct Student *next; }; int n; struct Student *creat(void)//定义函数返回一个指向链表头的指针 { struct Student *head

为PhoneGap写一个android插件

为PhoneGap写一个android插件,要怎么做? 其实这句话应该反过来说,为android写一个PhoneGap插件,要怎么做? 这里以最简单的Hello World!为例,做个说明: 1.第一步,要先建立一个支持PhoneGap(Cordova)的android工程 因为这个插件本质上是安卓插件,用于PhoneGap,因此,要二者支持才行,所以我们要建立一个支持PhoneGap(Cordova)的android工程,插件在这个工程里面编写. 扫盲:PhoneGap现在已经出售给了Apac

如何使用viewpager与fragment写一个app导航activity

今天我们来看一下如何使用viewpager和fragment组合来写一个app导航activity,这里使用到了android开源控件viewpagerindicator,有兴趣的同学可以去它网站上看看它的介绍. 附上效果截图一张: demo中只有一个activity,是用activity_main.xml来布局,其内容如下: <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:and