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

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

四:交互式

可以在终端输入代码并显示执行结果

'''
语言的语法是指组成正确的语句或表达式的顺序,语义指那些表达式或语句的内在含义。解释器流程
程序 => 解析 => 抽象语法树 => 执行(语义) => 结果1:解析语法
2:添加环境
3:执行
4:添加交互式
'''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 if isinstance(y, List) else [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)# ======== 交互式 ========# ======= 第四步 交互式 =======
def repl(prompt='lis.py> '):'''终端交互式,死循环读取终端输入字符串,执行,转换成字符串,打印到终端'''# 死循环读取终端输入while True: # 执行val = eval(parse(input(prompt)))    if val is not None: # 打印    print(schemestr(val))def schemestr(exp):"""将数据转位字符串"""if isinstance(exp, List):# 将列表中所有元素转成字符串,用空格分隔,开始结束加上括号return '(' + ' '.join(map(schemestr, exp)) + ')'else:# 转成字符串return Symbol(exp)# ======= 测试 ========
# 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 2) 0)'
# data_str = '(list? (list 2 3 4))'# a = tokenize(data_str)
# a =parse(data_str)
# print(a)
# a = eval(a)
# print(a)
repl()

这样的输入其实还有一点问题,比如不能什么也不输入就直接回车,输入一堆空格会报错,方向键不能上下左右,不能正常退出等等,所以我把程序稍微修改一下

先修改repl函数,解决不输入报错,方向键,不能正常退出的问题

# readline 给标准输入加缓存
import readline, sysdef repl(prompt='lis.py> '):'''终端交互式,死循环读取终端输入字符串,执行,转换成字符串,打印到终端'''# 像python的交互模式一样有一个提示sys.stderr.write('-*- lispy 0.01 -*- Quit using \'exit()\'\n')# 死循环读取终端输入while True:data_str = input(prompt)# 如果没有任何输入就不执行if data_str:# 像python一样输入exit()就退出程序,这样也就相当于把exit当作了关键字if data_str == 'exit()':sys.exit()# 执行val = eval(parse(data_str))if val is not None: # 打印print(schemestr(val))

然后修改 read_from_tokens 和 eval,解决多个空格报错问题

def read_from_tokens(tokens):if len(tokens) == 0:# 如下修改# return '长度为零!'print('不能输入空')return 
def eval(x, env=global_env):# 增加判断if x == None:return x

 

五:将Env改成class

'''
语言的语法是指组成正确的语句或表达式的顺序,语义指那些表达式或语句的内在含义。解释器流程
程序 => 解析 => 抽象语法树 => 执行(语义) => 结果1:解析语法
2:添加环境
3:执行
4:添加交互式
5: 将 Env 重定义为 Class
'''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)# ====== 第五步 重定义环境 =========# ====== 将Env环境定义成class =======class Env(dict):'''Env继承dict类'''def __init__(self, parms=(), args=(), outer=None):# 构造函数# 初始化环境,zip可以直接转化成dictself.update(zip(parms, args))# 外部环境self.outer = outerdef find(self, var):# 查找环境,这个是为了后面的局部环境和外部环境# 如果存在在局部环境中返回局部环境,没有就去外部环境找return self if (var in self) else self.outer.find(var)class Procedure(object):'''这个可以看作一个创造局部环境和外部环境的函数'''def __init__(self, parms, body, env):# 构造函数'''parms 是参数名body 是表达式env 是外部环境'''self.parms = parmsself.body = bodyself.env = envdef __call__(self, *args):# 调用对象的时候执行'''执行表达式,但是将上层的环境当作外部环境,将这一层的参数名与实际值设置为局部环境'''return eval(self.body, Env(self.parms, args, self.env))# ======= 第二步 设置环境 =======# ====== 环境 =======
'''
环境是指变量名与值之间的映射。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 if isinstance(y, List) else [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)# ======== 交互式 ========
def repl(prompt='lis.py> '):'''终端交互式,死循环读取终端输入字符串,执行,转换成字符串,打印到终端'''# 死循环读取终端输入while True: # 执行val = eval(parse(input(prompt)))    if val is not None: # 打印    print(schemestr(val))def schemestr(exp):"""将数据转位字符串"""if isinstance(exp, List):# 将列表中所有元素转成字符串,用空格分隔,开始结束加上括号return '(' + ' '.join(map(schemestr, exp)) + ')'else:# 转成字符串return Symbol(exp)# ======= 测试 ========
# 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 2) 0)'
# data_str = '(list? (list 2 3 4))'# a = tokenize(data_str)
# a =parse(data_str)
# print(a)
# a = eval(a)
# print(a)
repl()

这一步是为了后面的内容作准备的,光这么看并没看出比直接用dict有什么好的地方,特别是Procedure类,完全看不明白,关于这个类,我后面会详细说明一下

 

六:添加符合Schema的语法形式(quote,set!,lambda)

'''
语言的语法是指组成正确的语句或表达式的顺序,语义指那些表达式或语句的内在含义。解释器流程
程序 => 解析 => 抽象语法树 => 执行(语义) => 结果1:解析语法
2:添加环境
3:执行
4:添加交互式
5: 将Env重定义为Class
6:添加符合Schema的语法形式(quote,set!,lambda)
其实还有一个begin,它是按从左到右的顺序对表达式进行求值,并返回最终的结果,但是已经在环境中定义了
'''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)# ====== 第五步 重定义环境 =========# ====== 将Env环境定义成class =======class Env(dict):'''Env继承dict类'''def __init__(self, parms=(), args=(), outer=None):# 构造函数# 初始化环境,zip可以直接转化成dictself.update(zip(parms, args))# 外部环境self.outer = outerdef find(self, var):# 查找环境,这个是为了后面的局部环境和外部环境# 如果存在在局部环境中返回局部环境,没有就去外部环境找return self if (var in self) else self.outer.find(var)class Procedure(object):'''这个可以看作一个创造局部环境和外部环境的函数'''def __init__(self, parms, body, env):# 构造函数'''parms 是参数名body 是表达式env 是外部环境'''self.parms = parmsself.body = bodyself.env = envdef __call__(self, *args):# 调用对象的时候执行'''执行表达式,但是将上层的环境当作外部环境,将这一层的参数名与实际值设置为局部环境'''return eval(self.body, Env(self.parms, args, self.env))# ======= 第二步 设置环境 =======# ====== 环境 =======
'''
环境是指变量名与值之间的映射。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 if isinstance(y, List) else [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]# 这样可以根据调用去局部环境或者外部环境查找return env.find(x)[x]# 常量# elif isinstance(x, Number):#     # 数字就直接返回#     return x# 常量返回elif not isinstance(x, List):return x# if 语句,形式(if test conseq alt),test是判断条件,符合条件输出conseq,不符合输出altelif x[0] == 'if':# (if (> 1 2) 1 2)# 语句形式分别赋值(_, 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':# (define x 1)(_, symbol, exp) = x# 用递归计算数据# 将定义的数据加进环境中# 用递归是因为如果exp是个表达式,就计算出它的值再加入环境env[symbol] = eval(exp, env)elif x[0] == 'quote':# (quote (1 2))(_, exp) = x# quote 就是原样输出表达式return expelif x[0] == 'set!':# (set! x 1),注意这里的x必须先定义过,也就是环境中必须有(_, var, exp) = x# set! 其实是一个覆盖环境值的操作,用递归也是因为如果exp是个表达式,就计算出它的值再加入环境env.find(var)[var] = eval(exp, env)elif x[0] == 'lambda':# (lambda (x) (+ x x)),这样其实无法调用一般用(define xx (lambda (x) (+ x x))),(xx 3)(_, var, exp) = x'''这里就用到了Procedure类因为lambda匿名函数的参数都是局部的,外面是无法调用的,所以这里先保存参数名,表达式,将env当作外部环境(因为可能会用到外部环境的变量)当调用的时候将值传入,与参数名配对,当作局部环境'''return Procedure(var, 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)# ======== 交互式 ========
def repl(prompt='lis.py> '):'''终端交互式,死循环读取终端输入字符串,执行,转换成字符串,打印到终端'''# 死循环读取终端输入while True: # 执行val = eval(parse(input(prompt)))    if val is not None: # 打印    print(schemestr(val))def schemestr(exp):"""将数据转位字符串"""if isinstance(exp, List):# 将列表中所有元素转成字符串,用空格分隔,开始结束加上括号return '(' + ' '.join(map(schemestr, exp)) + ')'else:# 转成字符串return Symbol(exp)# ======= 测试 ========
# 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 2) 0)'
# data_str = '(list? (list 2 3 4))'# a = tokenize(data_str)
# a =parse(data_str)
# print(a)
# a = eval(a)
# print(a)
repl()

现在为止已经实现了一个简单的解释器

 

现在来说一下Procedure类和递归

第一次看这些代码的时候递归着实把我弄的有点晕头转向,然后就是Procedure类

先来看一下Procedure,我的理解:

# 以 (define twice (lambda (x) (* 2 x))) 为例解析 Procedure 类def eval(*args):# 最终执行的函数print('执行了')print('* 2 x:', args[0]*2)class Procedure(object):def __init__(self, parms, body, env):# 构造函数print('创造好了')self.parms, self.body, self.env = parms, body, envdef __call__(self, *args): # __call__相当于重载类后面的(),调用对象的时候__call__会被调用print('调用了')return eval(*args)# 当lambda的时候,因为用解释器的eval是递归的形式调用所以先执行的是这里
a = Procedure(1,2,3)# 当define的时候,相当于将类加入了环境
twice = {'twice': a}# 当用 (twice 4) 调用的时候,相当于调用对象传如参数
twice.get('twice')(4)

后来我又想了一种方式帮助理解,但是发现好像看着更麻烦了

# 以 (define twice (lambda (x) (* 2 x))) 为例解析 Procedure 类# 为了方便,我直接将递归执行表达式,再去环境中拿x的值这步,简化成已经知道参数名叫x直接取到值
def eval(body, env={}):# 最终执行的函数print('执行了')# 执行表达式print('* 2 x:', body(env.get('x')))class Procedure(object):def __init__(self, parms, body, env):# 构造函数print('创造好了')self.parms, self.body, self.env = parms, body, envdef __call__(self, var): # __call__相当于重载类后面的(),调用对象的时候__call__会被调用print('调用了')return eval(self.body, {self.parms: var})# 为了方便起见,表达式用函数代替
def _mul(x):return 2 * x
fn = _mul# 当lambda的时候,因为用解释器的eval是递归的形式调用所以先执行的是这里
a = Procedure('x', fn, {})# 当define的时候,相当于将类加入了环境
twice = {'twice': a}# 当用 (twice 4) 调用的时候,相当于调用对象传如参数
twice.get('twice')(4)

结果都是一样的

对于Procedure的理解,我个人认为是,当定义的时候,将参数名与整个lambda放进全局环境中,当执行lambda内部的时候将内部的参数保存在局部环境中

 

递归

这个程序中大量运用了递归,不论是解析语法,还是执行程序,递归一开始看着很绕,但是明白以后会发现它逻辑很清晰

# 递归
def recursion(num):print('执行')# 递归终止条件if num == 0:print('=归零=', num)else:print('传递'+'='*num+'》', num)# 递归,没有达到终止条件就一层一层的深入下去,达到终止条件以后再一层一层的返回recursion(num-1)# 递归每次返回后执行print('《'+'='*num+'归还', num)recursion(3)

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



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

相关文章

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

OpenCV实现实时颜色检测的示例

《OpenCV实现实时颜色检测的示例》本文主要介绍了OpenCV实现实时颜色检测的示例,通过HSV色彩空间转换和色调范围判断实现红黄绿蓝颜色检测,包含视频捕捉、区域标记、颜色分析等功能,具有一定的参考... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

Nexus安装和启动的实现教程

《Nexus安装和启动的实现教程》:本文主要介绍Nexus安装和启动的实现教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Nexus下载二、Nexus安装和启动三、关闭Nexus总结一、Nexus下载官方下载链接:DownloadWindows系统根

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

MySQL 横向衍生表(Lateral Derived Tables)的实现

《MySQL横向衍生表(LateralDerivedTables)的实现》横向衍生表适用于在需要通过子查询获取中间结果集的场景,相对于普通衍生表,横向衍生表可以引用在其之前出现过的表名,本文就来... 目录一、横向衍生表用法示例1.1 用法示例1.2 使用建议前面我们介绍过mysql中的衍生表(From子句