<编译原理 - 函数绘图语言解释器(1)词法分析器 - python>

背景

  • 编译原理上机实现一个对函数绘图语言的解释器 - 用除C外的不同种语言实现

  • 解释器分为三个实现块:

    • 词法分析器:用于识别一条语句中的关键词是否符合预先定义的规则。

    • 语法分析器:用来确定一条语句是否满足语法规则。

    • 解释器:用来确定满足语法规则的句子,在意思上是否符合要求。

  • 设计思路:

    1. 设计记号:词法分析器读取一个序列并根据构词规则把序列转化为记号流

    2. 定义一个字典:把所有符合一个模式的保留字、常量名、参数名、函数名等放进字典。字典是个数组,其元素的类型和记号的类型相同

    3. 设计程序的结构,具体见下面的代码

  • 用Pycharm写了三个.py文件:

    • scannerclass.py

    • scannerfunc.py

    • scannermain.py

    • 输入流是序列(存储在.txt文本),输出流是“字典”(一个个识别好的记号对象)

    • 测试文本序列(1):

      FOR T FROM 0 TO 2*PI STEP PI/50 DRAW(COS(t),sin(t));

    • 测试文本序列(2):

//------------------This is zhushi!!------------------------

ORIGIN IS (100,300); // Sets the offset of the origin

ROT IS 0; // Set rotation Angle.

SCALE IS (1,1); // Set the abscissa and ordinate scale.

FOR T FROM 0 TO 200 STEP 1 DRAW (T,0); // The trajectory of the x-coordinate.

FOR T FROM 0 TO 150 STEP 1 DRAW (0,-T); // The trajectory of the y-coordinate.

FOR T FROM 0 TO 120 STEP 1 DRAW (T,-T); // The trajectory of the function f[t]=t.

```

函数绘图语言介绍

  • 语句介绍

  • 函数绘图源程序举例介绍

  • 画出的图形介绍

Step 1 :scannerclass.py - 构造枚举类 记号类 符号表

from enum import Enum
import math Token_Type = Enum('Token_Type', ('ORIGIN', 'SCALE', 'ROT', 'IS', 'TO', 'STEP', 'DRAW', 'FOR', 'FROM', #保留字
'T', #参数
'SEMICO', 'L_BRACKET','R_BRACKET','COMMA', #分隔符
'PLUS','MINUS','MUL','DIV','POWER', #运算符
'FUNC', #函数符
'CONST_ID', #常数
'NONTOKEN', #空记号
'ERRTOKEN')) #出错记号 class Tokens: #记号类
#type:记号类别
#lexeme:输入的字符串/属性
#value:常数值
#funcptr:函数指针
def __init__(self,type,lexeme,value,funcptr):
self.lexeme=lexeme
self.value=value
self.funcptr=funcptr
if type in Token_Type:
self.type = type
else:
print("Invalid type") # 后续待填充 Alphabet=dict([('PI',Tokens(Token_Type.CONST_ID,"PI",3.1415926,None)), ## 符号表
('E',Tokens(Token_Type.CONST_ID,"E",2.71828,None)), ## 左key右value
('T',Tokens(Token_Type.T,'T',0.0,None)),
('SIN',Tokens(Token_Type.FUNC,'SIN',0.0,math.sin)), # math.sin / math.sinh
('COS',Tokens(Token_Type.FUNC,'COS',0.0,math.cos)),
('TAN',Tokens(Token_Type.FUNC,'TAN',0.0,math.tan)),
('LN',Tokens(Token_Type.FUNC,'LN',0.0,math.log)),
('EXP',Tokens(Token_Type.FUNC,'EXP',0.0,math.exp)),
('SQRT',Tokens(Token_Type.FUNC,'SQRT',0.0,math.sqrt)), # 后续操作待填充
('ORIGIN',Tokens(Token_Type.ORIGIN,'ORIGIN',0.0,None)),
('SCALE',Tokens(Token_Type.SCALE,'SCALE',0.0,None)),
('ROT',Tokens(Token_Type.ROT,'ROT',0.0,None)),
('IS',Tokens(Token_Type.IS,'IS',0.0,None)),
('FOR',Tokens(Token_Type.FOR,'FOR',0.0,None)),
('FROM',Tokens(Token_Type.FROM,'FROM',0.0,None)),
('TO',Tokens(Token_Type.TO,'TO',0.0,None)),
('STEP',Tokens(Token_Type.STEP, 'STEP', 0.0, None)),
('DRAW',Tokens(Token_Type.DRAW, 'DRAW', 0.0, None))])

Step 2 :scannerfunc.py - 构造词法分析器类

import scannerclass as sc
import os class scanner(): ##——————初始化词法分析器
def __init__(self,file_name): #输入要输入字符流的文件名
self.LineNo = 0 #记录字符所在行的行号
self.TokenBuffer = '' #待识别记号缓存区
self.file_name=r'C:\Users\62473\Desktop\\'+file_name #此处根据个人情况做调整
if os.path.exists(self.file_name):
self.fp = open(self.file_name, "r") #文件指针
else:
self.fp = None ##——————关闭词法分析器
def CloseScanner(self):
if self.fp!=None:
self.fp.close() ##——————从输入流中读入一个字符
def GetChar(self):
Char = self.fp.read(1)
return Char ##——————输入流回退一个字符
def BackChar(self,Char): ## 非二进制打开方式不能直接seek目前位置回溯,所以用tell()-1方式从头跳转前一位置
if Char != '':
self.fp.seek(self.fp.tell()-1) ##——————加入字符到TokenBuffer待识别字符串中
def AddCharToString(self,Char):
self.TokenBuffer+=Char ##——————清空TokenBuffer字符串
def EmptyString(self):
self.TokenBuffer='' ##——————识别的字符串查表
def JudgeKeyToken(self):
Token=sc.Alphabet.get(self.TokenBuffer,sc.Tokens(sc.Token_Type.ERRTOKEN,self.TokenBuffer,0.0,None))
return Token ##——————获取记号
# 此函数由DFA转化而来(有必要的话可以写个模拟dfa函数)此函数输出一个记号。每调用该函数一次,仅仅获得一个记号。
# 因此,要获得源程序的所有记号,就要重复调用这个函数。上面声明的函数都被此函数调用过
# 因为没有自定义变量,所以只需要查表不需要构造其他东西
# 输出一个记号,没有输入
def GetToken(self): Char = '' ##字符流
type = '' ##指向返回输出的Tokens对象
self.EmptyString() #清空缓冲区
while(1):
Char = self.GetChar()
if Char == '':
type = sc.Tokens(sc.Token_Type.NONTOKEN,Char,0.0,None)
return type
if Char == '\n':
self.LineNo=self.LineNo+1
if ~Char.isspace():
break
self.AddCharToString(Char) ##若不是空格、TAB、回车、文件结束符等,则先加入到记号的字符缓冲区中
if Char.isalpha():## 判断是否是英文
while(1):
Char = self.GetChar()
if Char.isalnum():
self.AddCharToString(Char)
else:
break
self.BackChar(Char)
type = self.JudgeKeyToken()
type.lexeme = self.TokenBuffer
return type elif Char.isdigit():
while(1):
Char = self.GetChar()
if Char.isdigit():
self.AddCharToString(Char)
else:
break
if Char == '.':
self.AddCharToString(Char)
while(1):
Char = self.GetChar()
if Char.isdigit():
self.AddCharToString(Char)
else:
break
self.BackChar(Char)
type = sc.Tokens(sc.Token_Type.CONST_ID,self.TokenBuffer,float(self.TokenBuffer),None)
return type else:
if Char == ';':
type = sc.Tokens(sc.Token_Type.SEMICO,Char,0.0,None)
elif Char == '(':
type = sc.Tokens(sc.Token_Type.L_BRACKET,Char,0.0,None)
elif Char == ')':
type = sc.Tokens(sc.Token_Type.R_BRACKET, Char, 0.0, None)
elif Char == ',':
type = sc.Tokens(sc.Token_Type.COMMA, Char, 0.0, None)
elif Char == '+':
type = sc.Tokens(sc.Token_Type.PLUS, Char, 0.0, None)
elif Char == '-': ##可能是行分割或减号
Char = self.GetChar()
if Char == '-':
while Char != '\n' and Char != '':
Char = self.GetChar()
self.BackChar(Char)
return self.GetToken()
else:
self.BackChar(Char)
type = sc.Tokens(sc.Token_Type.MINUS, '-', 0.0, None)
elif Char == '/': ##可能是注释分割或除号
Char = self.GetChar()
if Char == '/':
while Char != '\n' and Char != '':
Char = self.GetChar()
self.BackChar(Char)
return self.GetToken()
else:
self.BackChar(Char)
type = sc.Tokens(sc.Token_Type.DIV, '/', 0.0, None)
elif Char == '*':
Char = self.GetChar()
if (Char == '*'):
type = sc.Tokens(sc.Token_Type.POWER, '**', 0.0, None)
else:
self.BackChar(Char)
type = sc.Tokens(sc.Token_Type.MUL, '*', 0.0, None)
else:
type = sc.Tokens(sc.Token_Type.ERRTOKEN, Char, 0.0, None)
return type

Step 3 :scannermain.py - 完成I/O流

import scannerclass as sc
import scannerfunc as sf file_name = 'test.txt' ##放在桌面的测试文本
scanner = sf.scanner(file_name) if scanner.fp != None:
print(' 记号类别 字符串 常数值 函数指针\n')
print('——————————————————————')
while(1):
token = scanner.GetToken() #输出一个记号
if token.type == sc.Token_Type.ERRTOKEN: ##优化空格
#记号的类别不是错误或者空格,就打印出他的内容
continue
elif token.type != sc.Token_Type.NONTOKEN: ## 到了语法分析时这块需要改成ERRTOKEN,因为需要输出NONTOKEN
print("{:20s},{:>12s},{:12f},{}".format(token.type, token.lexeme,token.value,token.funcptr))
else:
break ## 文件结束符直接跳下一行读取数据放在语法分析器里面完成之前的bug
else:
print('Open Error!')

实现结果

  • 对于测试文本(1)FOR T FROM 0 TO 2*PI STEP PI/50 DRAW(COS(t),sin(t));的测试运行结果如下:

  • 换一组测试文本(2)进行的测试运行结果如下:

<编译原理 - 函数绘图语言解释器(1)词法分析器 - python>的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. node项目的基本目录结构

    1.public目录: 项目公共目录,存放静态资源(img.js.css)和公共资源,404错误提示页面: 2.routor目录: 路由控制器目录,存放路由文件,将所有的业务逻辑都都写在这里: 3.v ...

  2. 给idea设置默认使用的maven配置

    一,前言 大家都知道,java开发中最经常使用的开发工具是Maven,最近看新同事在使用idea,我也下载了一个,准备尝试一下. 而maven是非诚方便进行工程管理的,至少管理jar包,是非常方便的, ...

  3. js中的计时器事件`setTimeout()` 和 `setInterval()`

    js中的计时器事件 在js中,通常会有一些事件,我们需要让它 间隔一段时间之后再发生,或者 每隔一段时间 发生一次,那就需要用到我们js中的计时事件 计时事件主要有两种: setTimeout() - ...

  4. 4款黑科技级别的宝藏APP,能够轻松满足你的多种需求,请低调收藏

    有没有这样几款软件,在你每次一换新手机的时候就会立刻重新安装下来,感觉自己已经完全离不开它们?今天就来给大家分享几个非常好用的APP. 一.小羊搜搜 在生活中人人都有自己的爱好,无论你是喜欢影视.小说 ...

  5. 【django】form

    form 组件组要功能: 生成HTML标签 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页面显示内容 1.创建Form类时,主要涉及到 [字段] 和 [插件],字段用于 ...

  6. GetThreadTimes获取其它线程cpu时间

    http://www.cnblogs.com/eaglet/archive/2009/03/11/1408809.html 鄙视下上面的垃圾博文,纯粹忽悠人 参考文章: http://blog.kal ...

  7. linux 配置多个tomcat

    一.安装tomcat 1.下载链接:https://tomcat.apache.org/download-70.cgi,选择需要的版本下载(.tar.gz文件后缀) 2.通过Xshell.Xftp上传 ...

  8. Python采集VIP收费QQ音乐,一起来听周董最新的《说好不哭》,省3块不香吗?

    环境: windows python3.6.5 模块: requests selenium json re urllib 环境与模块介绍完毕后,就可以来实行我们的操作了. 第1步: 通过一个解析网站: ...

  9. IPv6系列-彻底弄明白有状态与无状态配置IPv6地址

    深入研究自动分配IPv6地址的Stateless(无状态)与Stateful(有状态)方式 小慢哥的原创文章,欢迎转载 目录 ▪ 一. Link-Local Address的生成方式 ▪ 二. Glo ...

  10. Python列表的深度排序

    实例1:>>>L = [2,3,1,4]>>>L.sort()>>>L>>>[1,2,3,4] 实例2:>>> ...