如何编写简单的parser(基础篇)

什么是parser?

简单的说,parser的工做便是将代码片断转换成计算机可读的数据结构的过程。这个“计算机可读的数据结构”更专业的说法是“抽象语法树(abstract syntax tree)”,简称AST。AST是代码片断具体语义的抽象表达,它不包含该段代码的全部细节,好比缩进、换行这些细节,因此,咱们可使用parser转换出AST,却不能使用AST还原出“原”代码,固然,能够还原出语义一致的代码,就如同将ES6语法的js代码转换成ES5的代码。javascript

parser的结构

通常来讲,一个parser会由两部分组成:html

  • 词法解析器(lexer/scanner/tokenizer)
  • 对应语法的解释器(parser)

在解释某段代码的时候,先由词法解释器将代码段转化成一个一个的词组流(token),再交由解释器对词组流进行语法解释,转化为对应语法的抽象解释,便是AST了。java

为了让你们更清楚的理解parser两部分的工做顺序,咱们经过一个例子来进行说明:git

437 + 734

在parser解析如上的计算表达式时,词法解析器首先依次扫描到“4”、“3”、“7”直到一个空白符,这时,词法解析器便将以前扫描到的数字组成一个类型为“NUM”的词组(token);接下来,词法解析器继续向下扫描,扫描到了一个“+”,对应输出一个类型为“PLUS”的词组(token);最后,扫描“7”、“3”、“4”输出另外一个类型为“NUM”的词组(token)。
clipboard.png
语法解释器在拿到词法解析器输出的词组流后,根据词组流的“NUM”,“PLUS”,“NUM”的排列顺序,解析成为加法表达式。express

由上的例子咱们能够看出,词法解析器根据必定的规则对字符串进行解析并输出为词组(token),具体表现为接二连三的数字组合(“4”、“3”、“7”和“7”、“3”、“4”)即表明了数字类型的词组;语法解释器一样根据必定的规则对词组的组合进行解析,并输出对应的表达式或语句。在这里,词法解析器应用的规则即为词汇语法(Lexical Grammar)的定义,语法解释器应用的规则即为表达式(Expressions)、语句(Statements)、声明(Declarations)和函数(Functions)等的定义。json

ECMAScript标准

看到这里你们可能会感受到奇怪,为何讲parser讲的好好的,又跑到ECMAScript标准上来了呢?由于以上提到的词汇语法(Lexical Grammar)、表达式(Expressions)、语句(Statements)、声明(Declarations)和函数(Functions)等都是ECMAScript标准中的所定义的,这其实也是ECMAScript标准的做用之一,即定义JavaScript的标准语法。segmentfault

词汇词法(Lexical Grammar)

ECMAScript的词汇词法规定了JavaScript中的基础语法,好比哪些字符表明了空白(White Space),哪些字符表明了一行终止(Line Terminators),哪些字符的组合表明了注释(Comments)等。具体的规定说明,能够在ECMAScript标准11章中找到。babel

这里咱们不仔细研读每一个语法的定义,只需知道词法解析器(lexer)判读词组(token)的依据来源于此便可,为了让你们有必定的了解,这里,咱们拿上面例子中的数字字面量(Numeric Literals)来进行说明:
ECMAScript标准中,对数字字面量的定义以下:
clipboard.png
该定义须要自上向下解读:数据结构

首先,规则定义了数字字面量(Numeric Literal)能够是十进制字面量(Decimal Literal)、二进制整数字面量(Binary Integer Literal)、八进制整数字面量(Octal Integer Literal)、十六进制整数字面量(Hex Integer Literal);ecmascript

在咱们的例子中,咱们只关心十进制的字面量,因此,接下来,规则定义十进制字面量(Decimal Literal)能够是包含小数点与不包含小数点的组合,这里咱们只需关注不包含小数点的定义,即十进制整数字面量(Decimal Integer Literal) + 可选的指数部分(Exponent Part);

最后,规则定义十进制整数字母量由非零数字(Non Zero Digit)+ 十进制数字(Decimal Digit)或十进制数字组(Decimal Digits)组成,非零数字是由1~9的数字组成,十进制数字是由0~9组成。

将上面的定义从新整合后,就能获得咱们须要的数字字面量的定义规则:

非零数字(1~9)+十进制数字组(0~9)

须要注意的是,这是简化版的数字字面量定义,完整版的须要加上以上规则中的全部分支条件。

表达式(Expressions)、语句(Statements)

ECMAScript标准12~13章包含了表达式和语句的相关定义,以前由词法解析器(lexer)处理后生成的词组流(token)交由语法解释器(parser)处理的主要内容,便是处理词组流构成的表达式与语句。在这里,咱们须要稍加明确一下表达式与语句之间的不一样与关系:

首先,语句包含表达式,大部分语句是由关键字+表达式或语句组成,而表达式则是由字面量(Literal)、标识符(Identifier)、符号(Punctuators)等低一级的词组组成;

其次,表达式通常来说会产生一个值,而语句不总有值。

理解第一点对于咱们写语法解释器很重要,因为语句是由表达式组成的,而表达式是有词组组成的,词组是有词法解析器进行解析生成的,因此,在语法解释器中,将以表达式为切入点,由表达式解析再深刻到语句解析中。

抽象语法树(AST)

了解一个parser的结构,以及parser解析语法所依赖的规则后,接下来,咱们须要了解一下一个parser所生产出来的结果——抽象语法树。在文章的开头,我有简单的解释抽象语法树便是具体代码片断的抽象表达,那它具体是长什么样的呢?

function sum (a , b) {
  return a+b;
}

以上的代码片断,AST树的描述以下(使用babylon7-7.0.0-beta.44,结果进行了简化):

{
      "type": "Program",
    "body": [
      {
        "type": "FunctionDeclaration",
        "id": {
          "type": "Identifier",
          "name": "sum"
        },
        "params": [
          {
            "type": "Identifier",
            "name": "a"
          },
          {
            "type": "Identifier",
            "name": "b"
          }
        ],
        "body": {
          "type": "BlockStatement",
          "body": [
            {
              "type": "ReturnStatement",
              "argument": {
                "type": "BinaryExpression",
                "left": {
                  "type": "Identifier",
                  "name": "a"
                },
                "operator": "+",
                "right": {
                  "type": "Identifier",
                  "name": "b"
                }
              }
            }
          ]
        }
      }
    ]
}

对该AST仔细观察一番,便会明白,AST其实便是咱们在已经ECMAScript标准对代码进行解析后,将标识符(identifier)、声明(declaration)、表达式(expression)、语句(statement)等按代码表述的逻辑整理成为树状结构。就拿上面的例子来讲,当语法解析器识别了一个二元表达式(Binary Expression),便将这个二元表达式所携带的信息——左值,右值,操做符按照固定的计算机可读的数据格式保存下来,便是咱们看到的AST树了。

固然,AST也须要具有固定的格式,这样计算机才能依照该格式阅读AST并进行接下来的编译工做,固然,有一些AST也被用来转义(如babel)。关于AST定义的规则,咱们能够参考babel的定义,这也是后面咱们实现parser时,所参考的标准。

接下来

理解完以上相关的知识,咱们便具有编写一个parser的先决条件了,那在下一章,咱们将实际操做一番,编写一个简易版本的JavaScript语言parser。

《如何编写简单的parser(实践篇)》

相关文章
相关标签/搜索