自制前端框架Web前端之路让前端飞

自制前段框架Day12.Function Call

2017-05-29  本文已影响50人  蚊子爸爸

端午的第一天我毛都没干,除了睡觉就是晃悠,还花了两三个小时在玩游戏。唯独看了看spring和mybatis,感觉也没什么可看的。今天睡觉睡到11点。现在我咬牙决定写会代码。
之前写到可以读取某个对象的属性了。今天打算做一下函数调用,假设对象的某个属性是一个funciton,应该可以调用这个function。这个功能也是核心功能,假设

$scope.hello=function(){
  console.log('hello')
}

那么在html中,应该是这样调用的:

<button ng-click="hello()">打印</button>

事实上测试案例就是这样:

it('Function Call', function() {
    var fn = parse('getName()');
    expect(fn({getName:function(){return 'wangji'}})).toBe('wangji');
  });

先想一下思路,对于这个字符串,产生的token是什么?应该是:getName,(,)这三个。先去补完一下这个词法分析。

 else if (this.is('{}[],:.()')) {//之前不能解析括号,现在加上
      this.tokens.push({
        text: currentChar,
        identifier: true
      })
      this.index++;

在AST阶段如果遇到括号,就应该产生一个Call类型的节点。

else if (next.text === "(") {
      primary = {type: ASTBuilder.CallExpression, callee: primary};
      this.consume(')');
    } else {

这个节点有一个属性是callee,因为既然是函数调用,肯定需要一个上下文。上下文就是左边的那个对象(左值和右值)。
现在可以脑子里想一下会生成一个什么样的AST树。

image.png

进入编译阶段的时候,先写一下针对ASTBuilder.CallExpression类型节点的写法。
下面这个思路已经很熟悉了,写过很多遍了,编译从AST的program开始,生成一个return xxxxx ; 的语句。然后进入AST.body节点,这个节点遇到的是CallExpression节点,需要return的是scope.callee()这个字符串。callee可以继续用recurse方法遍历。

 case ASTBuilder.CallExpression:
      var callee = this.recurse(ast.callee);
      return callee+"&&"+callee+"()";

跑测试的时候发现不对头,结果通过跟踪发现少了一部分。

var next;
  while ((next = this.expect('.', '[','('))) {//刚才忘了加左括号
    if (next.text === "[") {
      primary = {
        type: ASTBuilder.MemberExpression,
        object: primary,
        property: this.primary(),
        computed: true
      }
      this.consume(']');
    } else if (next.text === "(") {
      primary = { type: ASTBuilder.CallExpression, callee: primary };
      this.consume(')');
    } else {
成功

现在空函数是有了。下一步是解析函数的参数。在创建CallExpression节点的时候,应该还有一个属性是参数。
假设一下解析这个add(100,200)
自己先想一下生成的tokens应该是什么。然后在AST构建的时候:

primary = { type: ASTBuilder.CallExpression, callee: primary ,arguments:解析参数方法,this.parseArguments()};

parseArguments方法,再次提醒:primary方法用于把token转为节点

STBuilder.prototype.parseArguments = function () {
  var args = [];
  if (!this.peek(')')) {
    do {
      args.push(this.primary())
    } while (this.expect(','));
  }
  return args;
}

在编译的过程里,只需要把参数也编译出来就行了。思路很简单,建立一个arguments数组,然后把相应的节点都编译一下推入数组,最后加入到生成的函数代码中。

case ASTBuilder.CallExpression:
      var callee = this.recurse(ast.callee);
      var arguments=[];
      for(var i=0;i<ast.arguments.length;i++){
        arguments.push(this.recurse(ast.arguments[i]));
      }
      return callee + "&&" + callee + "("+arguments.join(",")+")";

最开始的时候觉得函数参数很麻烦,现在想一下思路,

$scope.add = function (a,b) {
    return a+b;
}

实际上生成的时候需要生成add(100,200)就可以了,不需要关注内在逻辑。现在跑一下试试。

image.png

成功。

上一篇下一篇

猜你喜欢

热点阅读