angularjs源碼筆記(5.1)--parse

簡介

ng提供一個 $parse 服務用於解析與scope值相關的字符串表達式,如:javascript

scope = {
  a: 1,
  b: 2
};

function fn ($parse) {
  var resFn = $parse('a + b + 1');
  resFn(scope); // == 4
}

能夠將字符串表達式中的變量映射到scope的變量上執行運算。java

$parse 的功能就是編譯器,將傳入的字符串表達式經過詞法、語法分析,最後編譯成跟 scope 及 locals 相關聯的代碼進行執行。express

因此,本文主要就 $parse  的工做原理進行解析,而非代碼的細節。數組

主結構

既然 $parse 是個service,那麼就有其對應的 provider.$get, 由其內代碼所知,涉及到的對象有 ParserLexer(詞法分析器)AST(語法分析器)ASTCompile(編譯器)ide

Parser.parse -> astCompiler.compile -> ast.ast -> lexer.lex
                                              |-> ast.program

各個方法的返回:源碼分析

  1. astCompiler.compile:返回一個function,供調用執行
  2. ast.ast::返回一個語法解析樹
  3. lexer.lex: 返回一個詞法分割數組

下面按主結構對源碼進行分析this

源碼分析

1. lexer 詞法分析

遊標進行逐個字符掃描,遇到不同的字符作不同的處理,如遇到 ' 或 " 表示字符串即開始讀取字符串,一直到對應的閉合符號 ' 或者 ",還有如遇到數字或者. 開頭就表示接下去是數字進行讀取數字操做。spa

while (this.index < this.text.length) {
  var ch = this.text.charAt(this.index);
  // 讀取字符串
  if (ch === '"' || ch === '\'') {
    this.readString(ch);
  } 
  // 讀取數字包含小數0.22及2e10這樣的
  else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
    this.readNumber();
  } 
  // 讀取標識符如變量等
  else if (this.isIdentifierStart(this.peekMultichar())) {
    this.readIdent();
  } 
  // 讀取(){}[]等符號
  else if (this.is(ch, '(){}[].,;:?')) {
    this.tokens.push({index: this.index, text: ch});
    this.index++;
  } 
  // 出去空白字符
  else if (this.isWhitespace(ch)) {
    this.index++;
  }
  // 讀取操做符+-*/ >= === !==等
  else {
    var ch2 = ch + this.peek();
    var ch3 = ch2 + this.peek(2);
    var op1 = OPERATORS[ch];
    var op2 = OPERATORS[ch2];
    var op3 = OPERATORS[ch3];
    if (op1 || op2 || op3) {
      var token = op3 ? ch3 : (op2 ? ch2 : ch);
      this.tokens.push({index: this.index, text: token, operator: true});
      this.index += token.length;
    } else {
      this.throwError('Unexpected next character ', this.index, this.index + 1);
    }
  }
}

全部的讀取操做如 readNumber 最終都會生成一個形以下面的對象,放入tokens 數組中翻譯

{
  index: start,
  text: number,
  constant: true,
  value: Number(number)
}

例如: code

str = obj.aa + '11';
aa = str.length > 2? 'abc':123

分解成 tokens (有些字段省略如index) :

[
  {identifier: true, text:'str'},
  {operator: true, text: '='},
  {identifier: true, text: 'obj'},
  {text: '.'},
  {identifier: true, text: 'aa'},
  {operator: true, text: '+'},
  {constant: true, text: '11',value: '11'},
  {text: ';'},
  {identifier: true, text:'aa'},
  {operator: true, text: '='},
  {identifier: true, text:'str'},
  {text: '.'},
  {identifier: true, text:'length'},
  {operator: true, text: '>'},
  {constant: true, text: '2', value: 2},
  {operator: true, text: '?'},
  {constant: true, text: 'abc', value: 'abc'},
  {operator: true, text: ':'},
  {constant: true, text: '123', value: 123}
]

2. AST 語法分析

對詞法分析返回的 tokens 進行語法分析,解析出以下結構的數據,能夠嵌套,或者說是一種樹結構:

{type: AST.xxx, xxx:xxx, yyy: {type: AST.xxx, xxx:xxx}}

type表示該字段的類型

AST.Program = 'Program'; // root節點
AST.ExpressionStatement = 'ExpressionStatement'; // 表達式節點 
AST.AssignmentExpression = 'AssignmentExpression'; // 賦值表達式:f=12+22
AST.ConditionalExpression = 'ConditionalExpression'; // 判斷表達式:
AST.LogicalExpression = 'LogicalExpression';  // 邏輯表達式
AST.BinaryExpression = 'BinaryExpression'; // 二元表達式:+-*/等
AST.UnaryExpression = 'UnaryExpression'; // 一元表達式: !a
AST.CallExpression = 'CallExpression';  // 調用表達式:fn()
AST.MemberExpression = 'MemberExpression'; // 成員變量:obj.prop1
AST.Identifier = 'Identifier';  // 標識符:變量等
AST.Literal = 'Literal'; // ture,false,null,undefined 常量
AST.ArrayExpression = 'ArrayExpression'; // 數組
AST.Property = 'Property'; // 對象屬性
AST.ObjectExpression = 'ObjectExpression'; //對象表達式:{a:11, b:12}
AST.ThisExpression = 'ThisExpression'; // this表達式: this.ff
AST.LocalsExpression = 'LocalsExpression'; // ??

根據運算符的優先級,將tokens進行翻譯,使用上面的例子,翻譯成以下object:

{
  AST.Program,
  body: [{
    type: AST.ExpressionStatement,
    expression: {
      type: AST.AssignmentExpression,
      left: {type: AST.Identifier, name: 'str'},
      operator: '=',
      right: {
        type: AST.BinaryExpression, 
        operator: '+',
        left: {
          type: AST.MemberExpression, 
          object: {
            type: AST.Identifier,
            name: 'obj'
          }, 
          property: {
            type: AST.Identifier,
            name: 'aa'
          }, 
          computed: false
        },
        right: {type: AST.Literal, value: '11'}
      }
    }
  },{
    type: AST.ExpressionStatement,
    expression: {
      type: AST.AssignmentExpression,
      left: {type: AST.Identifier, name: 'aa'},
      operator: '=',
      right: {
        type: AST.ConditionalExpression,
        test: {
          type: AST.BinaryExpression,
          operator: '>',
          left: {
            type: AST.MemberExpression, 
            object: {
              type: AST.Identifier,
              name: 'str'
            }, 
            property: {
              type: AST.Identifier,
              name: 'length'
            }, 
            computed: false
          },
          right: {type: AST.Literal, value: 2}
        },
        alternate: {type: AST.Literal, value: 'abc'}, 
        consequent: {type: AST.Literal, value: 123}
      }
    }
  }]
}

展開如圖就是一棵樹。

3. AST編譯

接下去作的就是就ast樹編譯成目標代碼,完成這項任務的function是 recurse 。

recurse 是個遞歸調用的方法,根據不同的ast對象作不同的字符串拼接處理,最簡單的如 Literal 的處理,就是直接將常量返回出來或者賦值給變量而後將變量返回出來。

簡單來講,如:

parse('123');

// 轉化爲
function () {
  return 123;
}

// 或
function () {
  var v0 = 123;
  return v0;
}


parse('ab.c=123');

// 轉化爲
function (s) {
  s.ab.c = 123;
}

本篇對於詞法及語法分析解析到這,再也不做過多的解讀,代碼層面也基本都是圍繞編譯原理的基本知識展開,因此對ng的總體的理念關聯不大,因此不一一解釋,對於目標代碼的編譯細節、插值表達式及watch的字符串解析下篇再詳細介紹。

相關文章
相關標籤/搜索