变量与作用域
3.1 简介
在上一章中,我们实现了一个简单的计算器。为了让它更像一门编程语言,我们还需要添加三个方面的内容:
- 1. 变量。用于操作状态。
- 2. 控制流。比如条件语句和循环语句。
- 3. 函数。用于代码复用。
以下是一些示例程序,展示了我们这门语言的语法:
;; 定义函数 `fib`,带有一个参数 `n`
(def fib (n)
;; 条件判断语句(if-then-else)
(if (le n 0)
(then 0)
(else (+ n (call fib (- n 1)))))) ;; 函数调用
(call fib 5)
;; `fib` 函数的另一个版本
(def fib (n) (do
(var r 0) ;; 变量声明
(loop (gt n 0) (do ;; 循环
(set r (+ r n)) ;; 变量赋值
(set n (- n 1))
))
(return r) ;; 从函数调用中返回
))
(call fib 5)
3.2 用于变量的新命令
一个程序是一系列操作状态的操作序列,所以我们将添加一个新的结构来执行一系列操作。do
命令会按顺序计算其参数,并返回最后一个参数。它还会为变量创建一个新的 “作用域”。
(do a b c...)
并且我们添加了两个新的命令,用于变量声明和赋值。
(var a init) ;; 创建一个带有初始值的新变量
(set a value) ;; 为变量赋一个新值
我还在这门语言中添加了注释。分号会忽略该行的剩余部分。通过扩展 skip_space
函数来处理注释:
def skip_space(s, idx):
while True:
save = idx
# 尝试跳过空白字符
while idx < len(s) and s[idx].isspace():
idx += 1
# 尝试跳过单行注释
if idx < len(s) and s[idx] == ';':
idx += 1
while idx < len(s) and s[idx] != '\n':
idx += 1
# 没有更多的空白字符或注释
if idx == save:
break
return idx
3.3 变量与作用域
变量是有作用域的 —— 它们只能被其同级表达式访问。在计算表达式时,我们将使用一个映射来存储变量。
问题在于,子表达式可能会定义与父作用域中变量名冲突的变量。通过使用每个作用域的映射,而不是全局映射来解决这个问题。
pl_eval
函数获得了一个新的参数 env
,用于存储变量。
def pl_eval(env, node):
# 读取一个变量
if not isinstance(node, list):
assert isinstance(node, str)
return name_loopup(env, node)[node]
...
env
参数是一个链表,它包含当前作用域的映射以及指向父作用域的链接。进入和离开一个作用域,只需添加或删除链表头部。
变量查找函数会向上遍历链表,直到找到变量名。
def name_loopup(env, key):
while env: # 链表遍历
current, env = env
if key in current:
return current
raise ValueError('未定义的名称')
计算新作用域的代码:创建一个新的映射,并将其链接到当前作用域。then
和 else
命令是 do
命令的别名。它们只是语法糖,这样你就可以写成 (if (then xxx) (else yyy))
而不是 (if xxx yyy)
。
def pl_eval(env, node):
...
# 新作用域
if node[0] in ('do', 'then', 'else') and len(node) > 1:
new_env = (dict(), env) # 将映射添加为链表头部
for val in node[1:]:
val = pl_eval(new_env, val)
return val # 最后一个元素
现在,var
和 set
命令的代码就很直观了。
def pl_eval(env, node):
...
# 新变量
if node[0] == 'var' and len(node) == 3:
_, name, val = node
scope, _ = env
if name in scope:
raise ValueError('名称重复')
val = pl_eval(env, val)
scope[name] = val
return val
# 更新变量
if node[0] =='set' and len(node) == 3:
_, name, val = node
scope = name_loopup(env, name)
val = pl_eval(env, val)
scope[name] = val
return val
3.4 测试
解释器接受一系列表达式,而不是单个表达式。这是通过用 do
命令包装输入来实现的。
def pl_parse_prog(s):
return pl_parse('(do'+ s + ')')
一个展示变量和作用域的示例程序:
def test_eval():
def f(s):
return pl_eval(None, pl_parse_prog(s))
assert f('''
;; 第一个作用域
(var a 1)
(var b (+ a 1))
;; a=1, b=2
(do
;; 新作用域
(var a (+ b 5)) ;; 名称冲突
(set b (+ a 10))
)
;; a=1, b=17
(* a b)
''') == 17
我们将在下一章中添加控制流和函数。
来源:GitHub
阅读剩余
版权声明:
作者:讳疾忌医-note
链接:https://www.1217zy.vip/archives/241
文章版权归作者所有,未经允许请勿转载。
THE END