【编译器实现笔记】5. 支持 let 和 命名函数

文章介绍了如何在Lisp方言中添加let关键字支持,包括通过IIFE实现变量定义,解析器和求值器的更新,以及对命名let和无名let的处理方式。
摘要由CSDN通过智能技术生成

原文地址: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);
       })());

即:

  1. 检测到 let 关键字
  2. let 后可能有一个名字(可选): loop
  3. 后面是变量定义列表:(x = 2, y = 3, z = x + y)。该列表也可能为空
  4. 再后面是定义体,可能是单一的表达式,也可能是 {} 包裹的表达式组: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));

即:

  1. 检测到 lambda 关键字
  2. lambda 后面支持一个函数名(可选):loop
  3. 如果指定了名字,则函数执行的环境必须定义这个名字并指向该函数。正如 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 了
}
  1. 如果 let 后是 var token,则其为 “命名 let”(named let)
    1. 用 delimited 函数并传入 parse_vardef 函数来解析(x = 2, y = 3, z = x + y)
    2. 返回一个调用命名函数表达式的 call 节点(即节点整体为一个 IIFE)。命名函数的参数名字为 let 中定义的变量,call 节点会负责传递参数值。函数体则是通过 parse_expression() 解析完成。
  2. 如果 let 后不是是 var token,则不是 “命名 let”
    1. 返回一个 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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值