资讯专栏INFORMATION COLUMN

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

Barry_Ng / 2622人阅读

摘要:在这里,词法解析器应用的规则即为词汇语法的定义,语法解释器应用的规则即为表达式语句声明和函数等的定义。如何编写简单的实践篇

什么是parser?

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

parser的结构

一般来说,一个parser会由两部分组成:

词法解析器(lexer/scanner/tokenizer)

对应语法的解释器(parser)

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

为了让大家更清楚的理解parser两部分的工作顺序,我们通过一个例子来进行说明:

437 + 734

在parser解析如上的计算表达式时,词法解析器首先依次扫描到“4”、“3”、“7”直到一个空白符,这时,词法解析器便将之前扫描到的数字组成一个类型为“NUM”的词组(token);接下来,词法解析器继续向下扫描,扫描到了一个“+”,对应输出一个类型为“PLUS”的词组(token);最后,扫描“7”、“3”、“4”输出另一个类型为“NUM”的词组(token)。

语法解释器在拿到词法解析器输出的词组流后,根据词组流的“NUM”,“PLUS”,“NUM”的排列顺序,解析成为加法表达式。

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

ECMAScript标准

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

词汇词法(Lexical Grammar)

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

这里我们不仔细研读每个语法的定义,只需知道词法解析器(lexer)判读词组(token)的依据来源于此即可,为了让大家有一定的了解,这里,我们拿上面例子中的数字字面量(Numeric Literals)来进行说明:
ECMAScript标准中,对数字字面量的定义如下:

该定义需要自上向下解读:

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

在我们的例子中,我们只关心十进制的字面量,所以,接下来,规则定义十进制字面量(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(实践篇)》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101303.html

相关文章

  • 如何编写简单parser(实践

    摘要:负责读取和记录当前代码的位置,并把读取到的代码交给处理,其意义在于,当传递给的代码需要进行判读猜测时,能够记录当前读取的位置,并在接下来的操作汇总回滚到之前的读取位置,也能在发生语法错误时,准确指出错误发生在代码段的第几行第几个字符。 上一篇(《如何编写简单的parser(基础篇)》)中介绍了编写一个parser所需具备的基础知识,接下来,我们要动手实践一个简单的parser,既然是简...

    shaonbean 评论0 收藏0
  • 如何构建一个分布式爬虫:基础

    摘要:继上篇我们谈论了的基本知识后,本篇继续讲解如何一步步使用构建分布式爬虫。到此,我们就实现了一个很基础的分布式网络爬虫,但是它还不具有很好的扩展性,而且貌似太简单了下一篇我将以微博数据采集为例来演示如何构建一个稳健的分布式网络爬虫。 继上篇我们谈论了Celery的基本知识后,本篇继续讲解如何一步步使用Celery构建分布式爬虫。这次我们抓取的对象定为celery官方文档。 首先,我们新建...

    ssshooter 评论0 收藏0
  • PostCSS自学笔记(一)【安装使用

    摘要:而则可制定个人需求的一套解决方案仅安装需要的插件。迫不及待的你已经等不及安装使用了吧。安装及使用一般是结合自动化工具使用,如果要单独使用可以安装,这里我先对的安装使用讲解下。接下来说点实际的,如何利用结合自动化工作在项目中使用。 PostCSS介绍 PostCSS是一个利用JS插件来对CSS进行转换的工具,这些插件非常强大,强大到无所不能。其中,Autoprefixer就是众多Post...

    jsummer 评论0 收藏0
  • Spring Cloud Alibaba基础教程:Nacos集群部署

    摘要:通过本文,我们将完成生产环境的搭建。第二步修改文件,增加支持数据源配置,添加目前只支持数据源的用户名和密码。另外,的集群需要个或个以上的节点,并且确保这三个节点之间是可以互相访问的。也可以故意的关闭某个实例,来验证集群是否还能正常服务。 前情回顾: 《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》 《Spring Cloud Alibaba基础教...

    elarity 评论0 收藏0
  • Spring Cloud Alibaba基础教程:Nacos集群部署

    摘要:通过本文,我们将完成生产环境的搭建。第二步修改文件,增加支持数据源配置,添加目前只支持数据源的用户名和密码。另外,的集群需要个或个以上的节点,并且确保这三个节点之间是可以互相访问的。也可以故意的关闭某个实例,来验证集群是否还能正常服务。 前情回顾: 《Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现》 《Spring Cloud Alibaba基础教...

    godruoyi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<