<编译原理 - 函数绘图语言解释器(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. 【深度学习】Focal Loss 与 GHM——解决样本不平衡问题

    Focal Loss 与 GHM Focal Loss Focal Loss 的提出主要是为了解决难易样本数量不平衡(注意:这有别于正负样本数量不均衡问题)问题.下面以目标检测应用场景来说明. 一些 ...

  2. 注意!GetThreadPriority的返回值不是系统的优先级值

    GetThreadPriority的返回值 Return code/value Description THREAD_PRIORITY_ABOVE_NORMAL 1 Priority 1 point ...

  3. 一台机器上搭建多个redis实例的配置文件修改部分

    1.单个redis服务搭建请参考:redis服务搭建 2.一台Redis服务器,分成多个节点,每个节点分配一个端口(6380,6381…),默认端口是6379. 每个节点对应一个Redis配置文件,如 ...

  4. 【SQL server基础】SQL视图加密,永久隐藏视图定义的文本

    SQL可以对视图进行加密.也就是,可永久隐藏视图定义的文本. 注意   此操作不可逆.加密视图后,无法再修改它,因为无法再看到视图定义.如果需要修改加密视图,则必须删除它并重新创建另一个视图. 示例代 ...

  5. 自定义属性--JavaScript

    1 - 获取属性值 element.属性 获取属性值 element.getAttribute('属性') 区别: element.属性 --获取内置属性(元素本身自带的属性) element.get ...

  6. jQuery鼠标滑过横向时间轴效果

    jQuery鼠标滑过横向时间轴效果---效果图: jQuery鼠标滑过横向时间轴效果---全部代码: <!DOCTYPE html> <html> <head> & ...

  7. CentOS7 Redis5.0.5环境搭建

    CentOS7 Redis5.0.5环境搭建 1基本环境配置 CentOS Linux release 7.6.1810 (Core) redis 5.0.5 1.下载解压redis.通过wget在官 ...

  8. 我又不是你的谁--java instanceof操作符用法揭秘

    背景故事 <曾经最美>是朱铭捷演唱的一首歌曲,由陈佳明填词,叶良俊谱曲,是电视剧<水晶之恋>的主题曲.歌曲时长4分28秒. 歌曲歌词: 看不穿你的眼睛 藏有多少悲和喜 像冰雪细 ...

  9. web动态站面试题

    1.简述 tomcat 的启动过程? 答:Tomcat 启动--> 读取自己的 server.xml-->根据 Context 标签的内容找到项目目录. 项目入口 path-->读取 ...

  10. 视频转换器 Wondershare Video Converter Ultimate v11.5.1 中文便携版

    Wondershare Video Converter Ultimate 是万兴公司出品的一款多功能音视频转换.DVD 刻录软件.视频下载软件.有了它,您可以随时随地观看.下载.编辑.转换.刻录视频, ...