前言
JISON 是什么
JISON 是一个基于 JavaScript 的解析器生成器(Javascript 版 BISON),它允许用户创建自己的语法解析器。JISON 可以处理复杂的语法结构,并生成可以解析这些结构的 JavaScript 代码。它类似于 YACC(Yet Another Compiler-Compiler)和 Bison,这些工具用于生成 C 语言的解析器。
简而言之,JISON可以解析复杂的文本结构,提取所需的内容。
原理
和之前研究的AST类似,JISON的工作原理基于解析器生成器的核心概念,它遵循编译原理中的解析技术。解析器生成器通常分为两个主要部分:语法分析器(Parser)生成器和词法分析器(Lexer)生成器。
在解析过程的开始,文本(通常是源代码)首先被词法分析器处理。词法分析器将输入的文本分解成一个个的“词法单元”(tokens),这些词法单元是语言中的基本元素,比如关键字、标识符、字面量、运算符等。词法分析器会根据定义好的词法规则来识别这些元素。
JISON通过读取一个包含语法规则的文件(通常是一个.jison文件),然后根据这些规则生成JavaScript代码。这个生成的代码就是实际的解析器,它可以用来解析符合这些规则的文本。
当解析器被调用时,它会读取输入文本,首先通过词法分析器将其分解为词法单元,然后通过语法分析器将这些词法单元组织成 AST。
使用
1. 安装&编译
使用 npm 安装:npm install jison -g
编译.jison文件:jison calculator.jison,若编译通过,这样会在同目录下生成同名.js文件,这个js文件即为解析器。
基础使用
1
2
3
4
5
6
7
import { parser } from "./jison/calculator";
// 解析表达式
const expression = "3 + 2 * 2";
const result = parser.parse(expression);
console.log("Result:", result);
2. 语法定义
2.1 词法规则
用于定义token的匹配规则,例如:
左侧是正则,右侧一般return别名,或是抛出非法token错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%lex
%%
\s+ /* skip whitespace */
[0-9]+("."[0-9]+)? return 'NUMBER';
"+" return '+';
"-" return '-';
"*" return '*';
"/" return '/';
"(" return '(';
")" return ')';
<<EOF>> return 'EOF';
. throw 'invalid character';
%%
2.2 语法规则
语法规则定义了如何将词法单元组织成有效的语句或表达式。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
%start Expression
%%
Expression
: Term
| Expression '+' Term { return ['+', $1, $3]; }
| Expression '-' Term { return ['-', $1, $3]; }
;
Term
: Factor
| Term '*' Factor { return ['*', $1, $3]; }
| Term '/' Factor { return ['/', $1, $3]; }
;
Factor
: NUMBER { return Number($1); }
| '(' Expression ')' { return $2; }
;
%%
表达式/产生式通过tokens组合,在{...}中编写action(对表达式的操作);
|表示此表达式可有多种表达式组合,表达式之间可以递归;
使用$1 $2 ...表示引用表达式;
$$代表当前产生式(production)的返回值。
return语句用于从 action 块中返回一个值。在 JISON 中,action 块默认返回 $$ 的值
在编写解析规则时,通常推荐使用$$ 来设置产生式的值,因为它更直观,也更符合 JISON 的使用习惯。只有在需要返回一个与默认值不同的值时,才使用 return 语句。
3. 注意
词法规则的定义顺序会影响匹配结果,注意可能相交的匹配规则,出现预期外的结果时可修改定义顺序或者进一步约束相应的正则表达式。
确定字符串的匹配规则需要通过""引用起来,如:"!>"
注意递归部分对EOF的处理(建议在顶层处理EOF),以及换行的处理
一个jison规则可以解析不同的语法格式, 但是对于同一段文本,只会匹配最先匹配的规则。即一个jison规则可以解析A或B规则,但是不能同时解析A和B。如:
可以解析lines和bracket的语法但是不能同时解析两者,若两者都有时,由于lines语法不包含bracket语法,会出现如:Expecting 'EOF', 'LINE', got 'LBRACKET',这样的错误
1
2
3
4
5
6
program
: lines EOF
{return $1;}
| bracket EOF
{return $1;}
;
在递归时容易出现 reduce 和 shift 歧义,可以通过改变终结符优先级来消除歧义:
1
2
3
%left '+' '-'
%left '*' '/'
%right '^'
在这个示例中,+ 和 - 操作符具有相同的优先级,且是左结合的。* 和 / 操作符具有更高的优先级,也是左结合的。^ 操作符优先级最高,且是右结合的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
%lex
%%
\s+ /* skip whitespace */
[0-9]+("."[0-9]+)?\b return 'NUMBER'
"-" return '-'
"+" return '+'
"*" return '*'
"/" return '/'
"^" return '^'
"(" return '('
")" return ')'
<<EOF>> return 'EOF'
. return 'INVALID'
/lex
%left '+' '-'
%left '*' '/'
%right '^'
%%
expression
: expression '+' expression
| expression '-' expression
| expression '*' expression
| expression '/' expression
| '-' expression %prec '^'
| '(' expression ')'
| NUMBER
;
%%
function parse(input) {
return parser.parse(input);
}