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

tech2022-07-12  188

四:交互式

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

''' 语言的语法是指组成正确的语句或表达式的顺序,语义指那些表达式或语句的内在含义。 解释器流程 程序 => 解析 => 抽象语法树 => 执行(语义) => 结果 1:解析语法 2:添加环境 3:执行 4:添加交互式 ''' import math import operator as op Symbol = 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 L else: # 返回字符,如果数字转成整数或者浮点类型,其它都是字符串 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,不符合输出alt elif 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, sys def 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 op Symbol = 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 L else: # 返回字符,如果数字转成整数或者浮点类型,其它都是字符串 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可以直接转化成dict self.update(zip(parms, args)) # 外部环境 self.outer = outer def 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 = parms self.body = body self.env = env def __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,不符合输出alt elif 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 op Symbol = 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 L else: # 返回字符,如果数字转成整数或者浮点类型,其它都是字符串 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可以直接转化成dict self.update(zip(parms, args)) # 外部环境 self.outer = outer def 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 = parms self.body = body self.env = env def __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,不符合输出alt elif 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 exp elif 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, env def __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, env def __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)

最新回复(0)