《计算机程序的构造和解释》阅读笔记:准备(1)【python3简单实现lisp解释器(1)】

本文主要是介绍《计算机程序的构造和解释》阅读笔记:准备(1)【python3简单实现lisp解释器(1)】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

打算深入阅读一下《计算机程序的构造和解释》,这应该会是一个比较漫长的过程,虽然这本书章节不多,但信息量挺大的,书的最后还会编写一个简单的解释器,也可以当作编译原理的简单入门。

这是对于《计算机程序的构造和解释》这本书阅读的准备,因为书用lisp语言,所以我先看了一下(How to Write a (Lisp) Interpreter (in Python))(如何用python实现一个lisp解释器)为什么用这个作为准备呢,因为这个代码量很少适合入门,也能让我了解一些lisp语法。

(How to Write a (Lisp) Interpreter (in Python))网站

完整的代码在网站最下面也可以下载

原本中应该使用python2编写的,因为apply,raw_input 在python3已经弃用,我会用python3编写,遇到这些python2的语法会修改掉

CSDN上有几篇译文,但是不知道为啥代码写错了,比如elif isinstance(x, Number):写成了elif not isinstance(x, Number):

解释器流程
程序(一段字符串) => 解析(语法) => 生成抽象语法树(语义) => 执行(语义) => 结果

一:解析

'''
语言的语法是指组成正确的语句或表达式的顺序,语义指那些表达式或语句的内在含义。解释器流程
程序 => 解析 => 抽象语法树 => 执行(语义) => 结果1:解析语法'''Symbol = str  # 字符串
List   = list  # 列表
Number = (int, float)  # 数字# ======= 第一步 解析语法 =======# ======== 解析语法 ========
def parse(program):'''语法解析函数返回抽象语法树,多维数组形式展示嵌套关系'''return read_from_tokens(tokenize(program))def tokenize(str):'''字符串转list词法分析:也就是将输入字符串分成一系列 token,返回一个语法列表'''# 默认空格分隔。所以需要将括号添加空格,以便分隔出来# 获取一个简单的语法列表,tokensreturn str.replace('(', ' ( ').replace(')', ' ) ').split()def read_from_tokens(tokens):'''解析list语义分析:将tokens组装成抽象语法树参数:语法列表返回:抽象语法树列表如果tokens长度为零直接返回去掉tokens第一个字符如果第一个字符是( ,定义一个空列表用来存放语法树,如果tokens第一个字符不是 ),循环将同级语法树存到列表,碰到 )直接去掉,返回这一层语法树其它情况返回字符,如果是数字就返回数字,其它的返回字符串'''if len(tokens) == 0:return '长度为零!'# list去掉第一位元素,如果不是( 就将它转换类型后加入列表中token = tokens.pop(0)# 第一个字符是(,然后建立表达式列表,直到匹配到 )if token == '(':# 这里定义空list是为了抽象语法树的嵌套关系L = []# 语法树第一个字符是)一定是错误语法while tokens[0] != ')':# 将括号以外的字符加进list中# 这里的递归是为了用多维数组展示抽象语法树嵌套关系,同级的字符放在一个list中L.append(read_from_tokens(tokens))# print(L)# 删除括号结束符 ),如果不删除的话,碰到 )就无法循环下去,只能得到部分语法树tokens.pop(0)# 返回同级数据return Lelse:# 返回字符,如果数字转成整数或者浮点类型,其它都是字符串return atom(token)def atom(token):'''字符串类型转换Lispy 的 tokens 是括号、标识符和数字'''try: return int(token)except ValueError:try: return float(token)except ValueError:return Symbol(token)# ======= 测试 ========
# data_str = '(define circle-area (lambda (r) (* pi (* r r))))'
# data_str = '(+ 1 1)'
# data_str = '(+ 2 (* 3 4))'
# data_str = '(+ 2 (* 3 (- 6 2)))'
# data_str = '(define (a x)(* x x))'
data_str = '(define fib (lambda (n) (if (n > 0)(- x) x)))'# a = tokenize(data_str)
a =parse(data_str)
print(a)

这里也有一点问题,就是开始括号多几个会报错,结束括号多几个语法树会少很多数据,于是我想加一个简单判断,括号对是否正确(提醒:这样写可能不兼容后面的代码,到时候还需要修改)

def tokenize(str):'''字符串转list词法分析:也就是将输入字符串分成一系列 token,返回一个语法列表'''# 默认空格分隔。所以需要将括号添加空格,以便分隔出来# 获取一个简单的语法列表,tokenstokens = str.replace('(', ' ( ').replace(')', ' ) ').split()# 判断括号对if tokens.count('(') != tokens.count(')'):return '括号意外结束'return tokens

read_from_tokens 函数中也加一个判断:

if not isinstance(tokens, list):return tokens

二:环境

'''
语言的语法是指组成正确的语句或表达式的顺序,语义指那些表达式或语句的内在含义。解释器流程
程序 => 解析 => 抽象语法树 => 执行(语义) => 结果1:解析语法
2:添加环境'''import math 
import operator as opSymbol = str  # 字符串
List   = list  # 列表
Number = (int, float)  # 数字
Env = dict  # 环境# ======= 第一步 解析语法 =======# ======== 解析语法 ========
def parse(program):'''语法解析函数返回抽象语法树,多维数组形式展示嵌套关系'''return read_from_tokens(tokenize(program))def tokenize(str):'''字符串转list词法分析:也就是将输入字符串分成一系列 token,返回一个语法列表'''# 默认空格分隔。所以需要将括号添加空格,以便分隔出来# 获取一个简单的语法列表,tokensreturn str.replace('(', ' ( ').replace(')', ' ) ').split()def read_from_tokens(tokens):'''解析list语义分析:将tokens组装成抽象语法树参数:语法列表返回:抽象语法树列表如果tokens长度为零直接返回去掉tokens第一个字符如果第一个字符是( ,定义一个空列表用来存放语法树,如果tokens第一个字符不是 ),循环将同级语法树存到列表,碰到 )直接去掉,返回这一层语法树其它情况返回字符,如果是数字就返回数字,其它的返回字符串'''if len(tokens) == 0:return '长度为零!'# list去掉第一位元素,如果不是( 就将它转换类型后加入列表中token = tokens.pop(0)# 第一个字符是(,然后建立表达式列表,直到匹配到 )if token == '(':# 这里定义空list是为了抽象语法树的嵌套关系L = []# 语法树第一个字符是)一定是错误语法while tokens[0] != ')':# 将括号以外的字符加进list中# 这里的递归是为了用多维数组展示抽象语法树嵌套关系,同级的字符放在一个list中L.append(read_from_tokens(tokens))# print(L)# 删除括号结束符 ),如果不删除的话,碰到 )就无法循环下去,只能得到部分语法树tokens.pop(0)# 返回同级数据return Lelse:# 返回字符,如果数字转成整数或者浮点类型,其它都是字符串return atom(token)def atom(token):'''字符串类型转换Lispy 的 tokens 是括号、标识符和数字'''try: return int(token)except ValueError:try: return float(token)except ValueError:return Symbol(token)# ======= 第二步 设置环境 =======# ====== 环境 =======
'''
环境是指变量名与值之间的映射。eval 默认使用全局环境,包括一组标准函数的名称(如 sqrt 和 max,以及操作符 *)
'''def standard_env():'''简单的标准环境环境是一个字典形式,将环境中添加标准库,或者自己定义的函数'''env = Env()  # 环境是一个字典形式# vars函数是python自带函数 返回对象object的属性名和属性值的字典形式env.update(vars(math))# 添加一些符合Scheme标准的环境env.update({'+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv,'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,'abs':     abs,'append':  op.add,  # 'apply':   apply,  # python3 没这个函数'begin':   lambda *x: x[-1],'car':     lambda x: x[0],'cdr':     lambda x: x[1:], 'cons':    lambda x,y: [x] + y,'eq?':     op.is_, 'equal?':  op.eq, 'length':  len, 'list':    lambda *x: list(x), 'list?':   lambda x: isinstance(x,list), 'map':     map,'max':     max,'min':     min,'not':     op.not_,'null?':   lambda x: x == [], 'number?': lambda x: isinstance(x, Number),   'procedure?': callable,'round':   round,'symbol?': lambda x: isinstance(x, Symbol),})# 返回这个环境return env# 作为全局环境
global_env = standard_env()# ======= 测试 ========
# data_str = '(define circle-area (lambda (r) (* pi (* r r))))'
# data_str = '(+ 1 1)'
# data_str = '(+ 2 (* 3 4))'
# data_str = '(+ 2 (* 3 (- 6 2)))'
# data_str = '(define (a x)(* x x))'
data_str = '(define fib (lambda (n) (if (n > 0)(- x) x)))'# a = tokenize(data_str)
a =parse(data_str)
print(a)

因为python3没有apply所以我们可以自己写一个

# python3 不存在apply,所以这里自己定义一个
def apply(func, *args, **kwargs):return func(*args, **kwargs)

上面环境中其实还有一些问题,比如'cons': lambda x,y: [x] + y,list和int是无法相加的,我去查了一下cons函数是一个将两个参数放到一个list中,所以我就改成了:

'cons': lambda x, y: [x] + y if isinstance(y, list) else [x] + [y],

等到执行函数写完以后可以用下面的例子测试一下:

(cons 1 2)(apply cons 1 1)(cons 1 (list 2 3))(length (cons 1 (list 2 3)))

 

python自带的加减乘除只能执行两个数的运算,我自己也改了一下,代替自带的四则运算就可以支持多参数运算了

def _add(*datas):sum_num = 0for data in datas:sum_num = sum_num + datareturn sum_numdef _sub(*datas):sub_num = 0for data in datas:if len(datas) > 1 and sub_num == 0:sub_num = dataelse:sub_num = sub_num - datareturn sub_numdef _mul(*datas):mul_num = 1for data in datas:mul_num = data * mul_numreturn mul_numdef _truediv(*datas):truediv_num = 1for data in datas:if truediv_num == 1:truediv_num = dataelse:truediv_num = truediv_num / datareturn truediv_num

 

三:执行

'''
语言的语法是指组成正确的语句或表达式的顺序,语义指那些表达式或语句的内在含义。解释器流程
程序 => 解析 => 抽象语法树 => 执行(语义) => 结果1:解析语法
2:添加环境
3:执行'''import math 
import operator as opSymbol = str  # 字符串
List   = list  # 列表
Number = (int, float)  # 数字
Env = dict  # 环境# ======= 第一步 解析语法 =======# ======== 解析语法 ========
def parse(program):'''语法解析函数参数:语句字符串返回:抽象语法树,多维数组形式展示嵌套关系'''return read_from_tokens(tokenize(program))def tokenize(str):'''字符串转list词法分析:也就是将输入字符串分成一系列 token,返回一个语法列表参数:语句字符串返回:一个简单的语法列表,tokens'''# 默认空格分隔。所以需要将括号添加空格,以便分隔出来return str.replace('(', ' ( ').replace(')', ' ) ').split()def read_from_tokens(tokens):'''解析list语义分析:将tokens组装成抽象语法树参数:语法列表返回:抽象语法树列表如果tokens长度为零直接返回去掉tokens第一个字符如果第一个字符是( ,定义一个空列表用来存放语法树,如果tokens第一个字符不是 ),循环将同级语法树存到列表,碰到 )直接去掉,返回这一层语法树其它情况返回字符,如果是数字就返回数字,其它的返回字符串'''if len(tokens) == 0:return '长度为零!'# list去掉第一位元素,如果不是( 就将它转换类型后加入列表中token = tokens.pop(0)# 第一个字符是(,然后建立表达式列表,直到匹配到 )if token == '(':# 这里定义空list是为了抽象语法树的嵌套关系L = []# 语法树第一个字符是)一定是错误语法while tokens[0] != ')':# 将括号以外的字符加进list中# 这里的递归是为了用多维数组展示抽象语法树嵌套关系,同级的字符放在一个list中L.append(read_from_tokens(tokens))# print(L)# 删除括号结束符 ),如果不删除的话,碰到 )就无法循环下去,只能得到部分语法树tokens.pop(0)# 返回同级数据return Lelse:# 返回字符,如果数字转成整数或者浮点类型,其它都是字符串return atom(token)def atom(token):'''字符串类型转换Lispy 的 tokens 是括号、标识符和数字'''try: return int(token)except ValueError:try: return float(token)except ValueError:return Symbol(token)# ======= 第二步 设置环境 =======# ====== 环境 =======
'''
环境是指变量名与值之间的映射。eval 默认使用全局环境,包括一组标准函数的名称(如 sqrt 和 max,以及操作符 *)
'''
def standard_env():'''简单的标准环境环境是一个字典形式,将环境中添加标准库,或者自己定义的函数'''env = Env()  # 环境是一个字典形式# vars函数是python自带函数 返回对象object的属性名和属性值的字典形式env.update(vars(math))# 添加一些符合Scheme标准的环境env.update({'+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv,'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,'abs':     abs,'append':  op.add,  # 'apply':   apply,  # python3 没这个函数'begin':   lambda *x: x[-1],'car':     lambda x: x[0],'cdr':     lambda x: x[1:], 'cons':    lambda x,y: [x] + [y],'eq?':     op.is_, 'equal?':  op.eq, 'length':  len, 'list':    lambda *x: list(x), 'list?':   lambda x: isinstance(x,list), 'map':     map,'max':     max,'min':     min,'not':     op.not_,'null?':   lambda x: x == [], 'number?': lambda x: isinstance(x, Number),   'procedure?': callable,'round':   round,'symbol?': lambda x: isinstance(x, Symbol),})# 返回这个环境return env# 作为全局环境
global_env = standard_env()# ======= 第三步 执行 ========# ======= 执行 ========
def eval(x, env=global_env):'''执行语义如果是字符,去环境中查找变量值或者函数如果是数字直接输出如果第一个字符是if先递归计算判断条件的值,然后递归计算输出值如果第一个字符是define递归计算出值,然后加入环境中其它的情况先用第一个字符去环境中查找函数,然后递归将后面的字符存入列表,最后执行函数参数是之前列表if 语句:(if test conseq alt)test是判断条件,如果符合输出conseq,否则输出alt例子:(if (> 1 0) 1 0)(if (> 1 0) (+ 1 2) (- 9 3))define 语句:(define symbol exp)例子:(define x 1)'''# 变量引用if isinstance(x, Symbol):# 如果是字符就去环境中找,返回变量值或者函数return env[x]# 常量elif isinstance(x, Number):# 数字就直接返回return x# if 语句,形式(if test conseq alt),test是判断条件,符合条件输出conseq,不符合输出altelif x[0] == 'if':# 语句形式分别赋值(_, test, conseq, alt) = x# 用递归计算条件exp = (conseq if eval(test, env) else alt)  return eval(exp, env)# define 定义变量,形式(define symbol exp),sybol是变量,exp是值elif x[0] == 'define':(_, symbol, exp) = x# 用递归计算数据# 将定义的数据加进环境中env[symbol] = eval(exp, env)    else:'''Schema 计算形式第一个元素是计算符号:(+ 1 2)'''# list第一个元素去环境里去找这个函数# print(x[0], 1111)proc = eval(x[0], env)# list后面的元素是函数参数  args = [eval(arg, env) for arg in x[1:]]# 将参数传入函数    return proc(*args)# ======= 测试 ========
# data_str = '(define circle-area (lambda (r) (* pi (* r r))))'
# data_str = '(+ 1 1)'
# data_str = '(+ 2 (* 3 4))'
# data_str = '(+ 2 (* 3 (- 6 2)))'
# data_str = '(define (a x)(* x x))'
# data_str = '(define s 1)'
data_str = '(begin (define r 10) (* pi (* r r)))'
# data_str = '(if (> 1 0) 1 0)'# a = tokenize(data_str)
a =parse(data_str)
print(a)
a = eval(a)
print(a)

可以看到不论是解析还是执行,程序中都使用了递归,递归在程序中可以清晰表现逻辑,虽然第一次看到的时候自己也是有点懵,不过跑一下数据基本就可以理解了,这也能帮助自己加深一下递归理解,毕竟现实项目中递归着实碰到的比较少!

 

这篇关于《计算机程序的构造和解释》阅读笔记:准备(1)【python3简单实现lisp解释器(1)】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1110767

相关文章

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

SpringBoot中使用Flux实现流式返回的方法小结

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连... 目录背景流式返回的核心概念与优势1. 提升用户体验2. 降低内存消耗3. 支持长连接与实时通信在Sp

Conda虚拟环境的复制和迁移的四种方法实现

《Conda虚拟环境的复制和迁移的四种方法实现》本文主要介绍了Conda虚拟环境的复制和迁移的四种方法实现,包括requirements.txt,environment.yml,conda-pack,... 目录在本机复制Conda虚拟环境相同操作系统之间复制环境方法一:requirements.txt方法

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja