<编译原理 - 函数绘图语言解释器(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. Java图片处理:ico格式转 PNG/JPG等格式

    一. 什么是ico图标? ico是一种图标格式,大量应用于网站,各个软件的logo或图标展示. 我们在进入某个网站或网页,它们上方标题左侧各自都带有logo图标. 这就是favicon.ico图标,它 ...

  2. Java String 类解析

    I.构造函数: public String() {} 默认构造函数 public String(String original) {} 使用原有字符串构造  public String(char va ...

  3. JAVASE知识点总结(四)

    第十七章:内部类和枚举 一.内部类 1.什么是内部类? 2.为什么要有内部类? 3.内部类的种类: 5.实例内部类 6.创建实例内部类 7.实例内部类注意点 8.访问变量原则: 4 9.静态内部类: ...

  4. poj-2232 New Stone-Forfex-Cloth Game 思维题

    Acm is a clever boy, and he developed a new game form the old Stone-Forfex-Cloth game. In this game, ...

  5. .net core 使用Rotativa创建PDF文档

    一.下载Rotaiva 工具  = >  NuGet包管理器  = >  管理解决方案的NuGet程序包 在打开的页面中搜索 Rotativa.AspNetCore 如下图: 选中红框的记 ...

  6. Spring IOC(1)----容器刷新(refresh())之前

    首先本次分析是基于注解形式的,想来xml格式的原理都是类似的. 首先说一下什么是Bean定义(beandefinition):bean定义并不是实例化的bean,而是bean对象的一些信息,根据这些定 ...

  7. 夯实Java基础系列15:Java注解简介和最佳实践

    Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...

  8. win7远程连接全屏和窗口模式切换

    最近开发需要win7远程连接,我知道在连接的时候可以设置全屏模式 但是进去之后想要切换就只能通过快捷键了上网查了一下是ctrl+alt+break.网上说的没有错.我查官方文档也是这样.但是我按的时候 ...

  9. 三个Eclipse下的Debug的使用场景(五)

    本文链接:https://blog.csdn.net/u011781521/article/details/55000066    http://blog.csdn.net/u010075335/ar ...

  10. JavaScript中闭包的使用和各种继承介绍

    一.什么是闭包?     (1)闭包的概念:a.闭包就是函数嵌套时,让局部变量变成自由变量的环境,是一种让局部变量进化的方式.                 b.定义在一个函数内部的函数.      ...