Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >聊聊PegJS

聊聊PegJS

作者头像
QQ音乐前端团队
发布于 2021-04-19 01:40:13
发布于 2021-04-19 01:40:13
1.5K00
代码可运行
举报
运行总次数:0
代码可运行

在开发前端BFF框架的时候,需要将团队后台使用的JCE协议(类似ProtoBuff协议)转换成nodejs对应的语法,这里参考@tencent/jce2node-cli的实现,使用PEG.js解析生成AST,下面就来介绍一下PEG.js是如何进行解析的?

我们在对文本进行解析的时候,通常可以使用正则表达式从目标文本中提取所需信息。但是仅使用正则表达式来解析,会发现非常难以阅读,可维护性比较差,而PegJs 则是一种更加简便可维护的 parser 工具。

PEG.js是一个JavaScript的词法解析器,可以用来处理复杂的数据或计算机语言,并轻松构建转换器、解释器编译器和其他工具。它的语法对前端工程师很友好,只需要掌握基本的正则语法即可,并提供在线体验网址。下面是基于PegJS语法的一个官方示例,它的语法有这样两个特点:

  • PegJS的语法由一组规则组成,从上至下进行解析。起始规则是整个语法的『根』,后面的所有规则定义都应该是这个『根』的子节点,如果某个规则无法从『根』溯源下去,那么这个规则就是一条无效的规则。
  • 规则形似变量声明,由名称和解析表达式组成。解析表达式可以是正则表达式,也可以是其他规则定义。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// additive.pegjs
start
  = additive

additive
  = left:multiplicative "+" right:additive { return left + right; }
  / 
  multiplicative

multiplicative
  = left:primary "*" right:multiplicative { return left * right; }
  / 
  primary

primary
  = integer
  / 
  "(" additive:additive ")" { return additive; }

integer "integer"
  = digits:[0-9]+ { return parseInt(digits.join(""), 10); }

上面的语法定义了加法和乘法的混合运算规则,可以将文本中符合规则的字符,比如 (2+7)*8,进行解析和运算。我们首先从简单的表达式开始分析:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interger = [0-9] // 只匹配一个数字
// "1" -> "1"
// "12" -> Line 1, column 2: Expected end of input but "2" found.

integer = [0-9]+ // 至少匹配一个数字
// "12" -> ["1", "2"]
// "" -> Line 1, column 1: Expected [0-9] but end of input found.

integer = [0-9]* // 匹配0个或者多个数字
// "124" -> ["1", "2", "4"]
// "" -> []

符号+表示至少匹配1个,符号*表示匹配0个或者多个。默认情况下,使用了+* 匹配出的结果会返回一个数组,PegJS 提供在表达式中通过变量名和一个format函数来自定义返回值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
integer = digits:[0-9] { return digits.join() }
// "124" -> "124"

我们再来看上面的"integer"规则,显然,这条规则匹配多个数字并返回number类型的返回值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
integer "integer"
  = digits:[0-9]+ { return parseInt(digits.join(""), 10); }
// "124" -> 124

这里来做一个小练习,匹配一个浮点数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
float = number:interger "." decimal:interger {return parseFloat(number + '.' + decimal);}
interger = digits:[0-9]+ { return digits.join(''); }
// "124.35" -> 124.35

再看一下符号/,它表示or 的含义。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
number = float / integer

为避免歧义,如果定义规则start = a / b,当输入即可以匹配a也可以匹配b,那么PegJS则优先使用a来进行解析。

下面介绍一个重要的概念:递归,这在描述嵌套或者树状结构的时候非常有用。先来看一个简单的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
commaSeparatedIntegerList
    = integer ',' commaSeparatedIntegerList
    / integer
integer = [0-9]

当解析输入"1,2"的时候,首先匹配了"1,",接下来"2"去递归匹配commaSeparatedIntegerList规则,发现符合integer表达式,最终的返回值是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[
   "1",
   ",",
   "2"
]

这里对匹配到的结果返回的value做一个说明:

  • 与文字字符串匹配的表达式会生成包含匹配文本的JavaScript字符串。
  • 与某个子表达式的重复出现匹配的表达式将生成具有所有匹配项的JavaScript数组。

最后我们再来看一下下面两条规则,它们的功能完全相同,第一条规则中定义了一个规则名称"数字",我们可以通过这种方式为规则起一个可读性高的名称:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interger "数字" 
	= [0-9]

interger = [0-9]

我们还可以在规则定义的最开始用"{"和"}"在大括号内部定义一些JavaScript代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  function makeInteger(digits) {
    return parseInt(digits.join(""), 10)
  }
}
interger = digits:[0-9]+ { return makeInteger(digits); }

以上就是 PegJS 语法的基本使用方法,我们可以用它来定义各种复杂的解析规则。

现在我们开始尝试去解析一个简单的JCE文件吧,它主要由两部分组成:structinterfacestruct定义了数据类型,interface 中则声明了服务的方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module MTT {
    struct HelloReq {
        0   require int id;
    };

    struct HelloRsp {
        0   require int     iCode;
        1   require string  sMessage;
    };

    interface Hello {
        int hello (HelloReq req, out HelloRsp rsp);
    };
};

首先定义struct的规则,它里面包含多个成员,每个成员由序号、关键字、类型、变量名和分号组成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
StructDefinition "struct"
  = "struct" _+ id: Identifier _* "{" _* members: MemberDeclaration+ _* "}" _* ";" _* {
    return {
      id,
      type: "struct",
      members
    }
  }
    
MemberDeclaration "member"
  = i: IntegerLiteral _+ 
    key: ("require" / "optional") _+ 
    type: TypeSpecifier _+ 
    id: Identifier _* ";" _*  {
      return {
        index: i,
        isRequired: key === "required",
        id,
        type,
        id
      };
    }
    
IntegerLiteral
  = digits: [0] { return parseInt(digits); }
  / head: [1-9] tail:[0-9]* { return parseInt([head, ...tail].join('')); }
  
Identifier
  = head: [_a-zA-Z] tail: [_a-zA-Z0-9]* {
    return [head, ...tail].join('');
  }

// 这里TypeSpecifier的定义仅为示例,没有罗列出所有的类型定义
TypeSpecifier
  = "void" / "bool" / "string" / "int" / "short" / type: "unsigned" blank "int" { return type.join("") } 
    
blank
  = [ ]+ {
    return "";
  }

_ "whitespace"
  = ([ \t\n\r]) {
    return "";
  }

通过上面的规则,我们就可以得到结构化的struct了。

struct.png

接下来用类似的思路来定义interface的规则:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
InterfaceDefinition "interface"
	= "interface" _+ id: Identifier _* "{" _* methods: MethodDeclaration+ _* "}" _* ";" _* {
    return {
    	id,
      type: "interface",
      methods
  	}
  }
  
MethodDeclaration "method"
	= returnType: TypeSpecifier _+ id: Identifier _* "(" _* params: ParameterDefinition _* ")" _* ";" _* {
    return {
    	id,
    	type: "method",
      returnType,
    	params
  	}
  }
  
ParameterDefinition
	= first: SingleParameterDefinition _* "," _* left: ParameterDefinition {
    return [first, ...left]
  } / 
  param: SingleParameterDefinition { return [param]; }
  
  
SingleParameterDefinition
	= "out" _+ type: (Identifier / TypeSpecifier)  _+ id: Identifier {
    return {
      id,
      io: "out",
      type
    }
  } 
  / 
  _* type: (Identifier / TypeSpecifier) _+ id: Identifier {
    return {
      id,
      io: "",
      type
    }
  }

那么就得到了解析后的接口方法定义:

interface.png

最后,将struct规则和interface规则进行整合后,就可以得到一个简单的Pegjs语法的JCE解析器了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
jce
	= module: ModuleDefinition { return module; }

ModuleDefinition
	= _* "module" _+ id: Identifier _* "{" _* value: ValueDefinition+ _* "}" _* ";" _* {
    return {
    	type: "module",
      id,
    	value,
  	}
  }
  
ValueDefinition = StructDefinition / InterfaceDefinition

最终整个JCE文件的解析结果如下:

jce.png

参考文献:

Intro to Peg.js

documentation#grammar-syntax-and-semantics

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 QQ音乐前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
手摸手实现一个编译器(上)
PEG.js 是一个简单的 JavaScript 解析器生成器,可以生成具有出色错误报告的快速解析器。您可以使用它来处理复杂的数据或计算机语言,并轻松构建转换器、解释器、编译器和其他工具。
码农小余
2022/06/16
8160
手摸手实现一个编译器(上)
编译原理工程实践—03使用递归下降算法实现简易语法分析器
语法分析的目的是为了继续识别出程序结构,方便计算机的理解和执行。本章将在前面词法分析器基础上,实现一个简单的语法分析器,进一步处理解析出的 Token,最终生成一棵抽象语法树 AST。
CS逍遥剑仙
2025/05/12
2130
手写一个解析器
作者:jolamjiang,腾讯 WXG 前端开发工程师 前言 最近工作中有一些同学在做一些效能工具的时候遇到需要写一门领域相关语言(DSL)及其解析器的场景,笔者恰好有相关的经验向大家指一下北。 首先请问一下大家有没有想过这个功能怎么做? 点击播放视频 本文将围绕如何实现类似于 Excel 中 =C1+C2+"123" 这样子的表达式的功能这一例子,在不需要编译原理的相关知识的前提下,用写正则表达式作为类比,借助一个工具库,讲述实现一个领域相关语言的解析器的一般步骤,让你能够快速实现一个解析器。
腾讯技术工程官方号
2020/05/27
1.3K0
TiDB SQL Parser 的实现
其中,SQL Parser的功能是把SQL语句按照SQL语法规则进行解析,将文本转换成抽象语法树(AST),这部分功能需要些背景知识才能比较容易理解,我尝试做下相关知识的介绍,希望能对读懂这部分代码有点帮助。
mazhen
2023/11/24
6930
TiDB SQL Parser 的实现
TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现
PingCAP 发布了 TiDB 的源码阅读系列文章,让我们可以比较系统的去学习了解TiDB的内部实现。最近的一篇《SQL 的一生》,从整体上讲解了一条 SQL 语句的处理流程,从网络上接收数据,MySQL 协议解析和转换,SQL 语法解析,查询计划的制定和优化,查询计划执行,到最后返回结果。
PingCAP
2018/03/22
4.7K3
TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现
PEG.js学习笔记
本文介绍了PEG.js,一种JavaScript的表达式语法解析器,可用于快速分析数据或建立复杂计算机程序。PEG.js可以用于快速解析形成抽象语法树,从而进行代码生成、代码优化、代码解析等任务。PEG.js支持多种编程范式,包括函数式、命令式、面向对象等,具有易学易用、高性能、高度可扩展等特点。PEG.js还提供了丰富的库和工具,包括语法分析、数据转换、词法分析、代码生成等,可广泛应用于各种场景。同时,PEG.js还提供了高度可配置的插件系统,可以灵活扩展其功能。
IMWeb前端团队
2018/01/08
1.2K0
golang源码分析(19)简单编译器-计算器
我们运行这个测试,毫无疑问会失败。不过没关系,我们先把这个测试放到一边,我们从编译器最简单的开始。
golangLeetcode
2022/08/02
7380
使用普拉特解析法解析复杂的算术表达式
上一节我们实现了编译原理中语法解析入门,能够解析简单的由let关键字开头的变量定义语句,现在我们再接再厉,实现解析由return 开头的返回值语句。由于return 后面可以跟着一个变量,一个数值,一个函数调用,以及一个带有操作符的计算式,这几种情况,我们统一用算术表达式来归纳。因此对应于return 语句的语法解析表达式是: ReturnStatement := return Expression 为了简单起见,我们代码实现时,任然假设return 后面跟着一个数字字符串,后面我们会深入探讨如何解析异常复
望月从良
2018/07/19
1.5K0
Golang 编译原理 计算器(通俗易懂)
本文不需要你掌握任何编译原理的知识。 只需要看懂简单的golang语言即可, 完整的代码示例在GIT
李海彬
2019/03/07
1K0
Golang 编译原理 计算器(通俗易懂)
一文打透前端研发需要了解的DSL
注意,本文有些难度,不太适合新手阅读,时候有一定编码经验的人阅读。废话应有点多了,ok,我们开始吧。
老码小张
2024/04/08
4.1K0
一文打透前端研发需要了解的DSL
leetcode 新题型----SQL,shell,system design
leetcode 主要是一个针对北美的coder人群找工作的代码练习网站,我在2015年初次接触这个网站的时候,总共只有200多道题目,是一个类似acm 的a题网站。这些年变化越来越大,主要是因为找工作当然是多样化的考核过程,leetcode 也逐渐与时俱进,推出了下面几个类别的练习,今天我们随便挑几个练习一下:
流川疯
2019/01/18
1.3K0
探究Presto SQL引擎(1)-巧用Antlr
自2014年大数据首次写入政府工作报告,大数据已经发展7年。大数据的类型也从交易数据延伸到交互数据与传感数据。数据规模也到达了PB级别。
冬夜先生
2021/10/12
1.8K0
编译原理工程实践—04处理语义分析实现简易脚本解释器
上一章实现的简易语法分析器能够解析简单的表达式、变量声明和初始化语句、赋值语句,生成简化的AST。但距离一门真正的语言还相差甚远,例如未处理作用域、面向对象等等特性,这些往往是在语义分析阶段来处理的,本章将讲述语义分析的实现。
CS逍遥剑仙
2025/05/12
1150
iOS小技能:NSPredicate在正则表达式的应用
=, ==The left-hand expression is equal to the right-hand expression.>=, =>The left-hand expression is greater than or equal to the right-hand expression.<=, =<The left-hand expression is less than or equal to the right-hand expression.>The left-hand expression is greater than the right-hand expression.<The left-hand expression is less than the right-hand expression.!=, <>The left-hand expression is not equal to the right-hand expression.
公众号iOS逆向
2022/08/22
9130
iOS小技能:NSPredicate在正则表达式的应用
人人都能读懂的编译器原理
理解编译器内部原理,可以让你更高效利用它。按照编译的工作顺序,逐步深入编程语言和编译器是怎样工作的。本文有大量的链接、样例代码和图表帮助你理解编译器。
马哥linux运维
2018/12/26
1.7K0
人人都能读懂的编译器原理
探究Presto SQL引擎(1)-巧用Antlr
自2014年大数据首次写入政府工作报告,大数据已经发展7年。大数据的类型也从交易数据延伸到交互数据与传感数据。数据规模也到达了PB级别。
2020labs小助手
2021/08/10
2.3K0
「 giao-js 」用js写一个js解释器
词法分析是由词法分析器完成的,词法分析器会扫描(scanning)代码,提取词法单元。
null仔
2020/12/02
46.9K0
「 giao-js 」用js写一个js解释器
手摸手实现一个编译器(中)
上篇我们了解了 PEG.js 的基础使用,忘记的童鞋建议复习一下,对于本文的食用效果会更佳哦!
码农小余
2022/06/16
5880
手摸手实现一个编译器(中)
解释器模式 Interpreter 行为型 设计模式(十九)
如果形势变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无穷种组合方式
noteless
2018/12/26
5650
Mac下利用Flex和Bison实现控制台计算器
我们借助Flex和Bison对给定的表达式进行词法和语法分析,并在语法分析的同时完成相应的计算。
里克贝斯
2021/05/21
1.9K0
Mac下利用Flex和Bison实现控制台计算器
相关推荐
手摸手实现一个编译器(上)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验