原文地址:https://lisperator.net/pltut/
定义新变量
λanguage 没有办法定义新变量
例如,我们希望新定义一个变量 z,使得 z=x+y
我们只能使用一个立即执行函数 IIFE(Immediately-Invoked Function Expression)来定义 z
(λ(x, y){
(λ(z){ ## 当 y 受到 x 的影响时就更糟糕了
print(x + y + z);
})(x + y);
})(2, 3);
目标
支持 let
关键字
# 无名 let
let (x = 2, y = 3, z = x + y) print(x + y + z)
=>
(λ(x){
(λ(y){
(λ(z){
print(x + y + z);
})(x + y);
})(3);
})(2);
# 命名 let
print(let loop (n = 10)
if n > 0 then n + loop(n - 1)
else 0);
=>
print((λ(loop){
loop = λ(n) if n > 0 then n + loop(n - 1)
else 0;
loop(10);
})());
即:
- 检测到 let 关键字
- let 后可能有一个名字(可选): loop
- 后面是变量定义列表:(x = 2, y = 3, z = x + y)。该列表也可能为空
- 再后面是定义体,可能是单一的表达式,也可能是 {} 包裹的表达式组:if n > 0 then n + loop(n - 1) else 0);
命名函数
print((λ(loop){
loop = λ(n) if n > 0 then n + loop(n - 1)
else 0;
loop(10);
})());
=>
print((λ loop (n) if n > 0 then n + loop(n - 1)
else 0)
(10));
即:
- 检测到 lambda 关键字
- lambda 后面支持一个函数名(可选):loop
- 如果指定了名字,则函数执行的环境必须定义这个名字并指向该函数。正如 JavaScript 中的命名函数表达式一样。
分词器(Tokenizer)更新
修改 function TokenStream
在关键字列表增加 let
var keywords = ' let if then else lambda λ true false ';
解析器(Parser)更新
修改 parse_lambda
更改解析器中的 parse_lambda 函数来支持函数名:
function parse_lambda() {
return {
type: 'lambda',
// 原本这个字段没有,给 lambda 类型的对象加了一个 name 字段
name: input.peek().type == 'var' ? input.next().value : null,
vars: delimited('(', ')', ',', parse_varname),
body: parse_expression(),
};
}
增加 parse_let
function parse_let() {
skip_kw('let');
// 是命名 let 的情况
// let funName (x = 2, y = 3, z = x + y) print(x + y + z)
if (input.peek().type == 'var') {
var name = input.next().value;
// 解析 (x = 2, y = 3, z = x + y)
// parse_vardef 是一个函数,用于把各个变量定义分开
// 解析的结果是
// [
// {name:x; def:2},
// {name:y; def:3},
// {name:z; def:x+y},
// ]
var defs = delimited('(', ')', ',', parse_vardef);
return {
type: 'call',
func: {
type: 'lambda',
name: name, // funName
vars: defs.map(function (def) {
return def.name;
}), // [x,y,z]
body: parse_expression(), // print(x+y+z)
},
args: defs.map(function (def) {
return def.def || FALSE;
}), // [2,3,x+y]
};
// return 了一个 IIFE
// # call 的那个函数 # 参数
// (λ funName(x,y,z) print(x+y+z) )(2,3,x+y)
}
// 不是命名 let 的情况
// let (x = 2, y = 3, z = x + y) print(x + y + z)
return {
type: 'let',
// 解析 (x = 2, y = 3, z = x + y)
// parse_vardef 是一个函数,用于把各个变量定义分开
// 解析的结果是
// [
// {name:x, def:2},
// {name:y, def:3},
// {name:z, def:x+y},
// ]
vars: delimited('(', ')', ',', parse_vardef),
body: parse_expression(), // print(x+y+z)
};
// 这是直接返回了一个 type 为 let 的对象,不把它看作是 FIFE 了
}
- 如果 let 后是 var token,则其为 “命名 let”(named let)
- 用 delimited 函数并传入 parse_vardef 函数来解析(x = 2, y = 3, z = x + y)
- 返回一个调用命名函数表达式的 call 节点(即节点整体为一个 IIFE)。命名函数的参数名字为 let 中定义的变量,call 节点会负责传递参数值。函数体则是通过 parse_expression() 解析完成。
- 如果 let 后不是是 var token,则不是 “命名 let”
- 返回一个 let 节点,持有 vars 和 body 属性。vars 的结构为 { name: VARIABLE, def: AST },会由下面函数从 let 定义列表中解析获得:
// 解析 x = 2
function parse_vardef() {
var name = parse_varname(),
def;
if (is_op('=')) {
input.next();
def = parse_expression();
}
return { name: name, def: def };
// 返回 {name:x, def:2}
}
修改 parse_atom(调度器)
在调度器(parse_atom)添加一行
if (is_kw('let')) return parse_let();
求值器(Evaluator)更新
如果没有增加语义,而是通过修改解析器,把新的语法解析成已有的语法,这被称为 语法糖
(syntactic sugar)
将新语法转变为已有 AST 节点的操作就称为 脱糖
(desugaring)
我们倾向于修改 AST 而不是 脱糖
,所以必须要更新求值器。
修改 make_lambda
支持可选的函数名
function make_lambda(env, exp) {
// if 部分为新增
// 如果有函数名,则当闭包创建的时侯会立即扩展作用域,并定义该名字指向新创建的闭包。剩下的不变。
if (exp.name) {
env = env.extend();
env.def(exp.name, lambda);
}
function lambda() {
var names = exp.vars;
var scope = env.extend();
for (var i = 0; i < names.length; ++i)
scope.def(names[i], i < arguments.length ? arguments[i] : false);
return evaluate(exp.body, scope);
}
return lambda;
}
修改 evaluate(求值函数)
增加支持 let AST 节点
case "let":
// 对于每个定义的变量,需要扩展一次作用域
exp.vars.forEach(function(v){
var scope = env.extend();
// 如果该变量有初始化,则初始值为 = 后面进行求值的结果
// 否则初始值为 false
scope.def(v.name, v.def ? evaluate(v.def, env) : false);
env = scope;
});
// 对 let 定义体进行求值
return evaluate(exp.body, env);