自己动手实现PHP8的match语法

我们现在来实现一下PHP8match语法。大概形如:

1
2
3
4
echo match (1) {
1 => 2
2 => 3
}

首先,我们来看看这里有哪些token

1
2
3
4
5
6
7
8
1. echo 对应 T_ECHO
2. match 对应 T_MATCH
3. ( 对应 T_LEFT_PARENTHESIS
4. ) 对应 T_RIGHT_PARENTHESIS
5. 1 对应 T_NUMBER
6. => 对应 T_DOUBLE_ARROW
7. { 对应 T_LEFT_BRACE
8. } 对应 T_RIGHT_BRACE

所以,我们可以很轻易的写出解析token的规则(match.l):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%{
#include "match.tab.h"
%}

%%
echo {return T_ECHO;}
match {return T_MATCH;}
[(] {return T_LEFT_PARENTHESIS;}
[)] {return T_RIGHT_PARENTHESIS;}
[{] {return T_LEFT_BRACE;}
[}] {return T_RIGHT_BRACE;}
[0-9]+ {yylval = atoi(yytext); return T_NUMBER;}
=> {return T_DOUBLE_ARROW;}
\n /* ignore end of line */
[ \t]+ /* ignore whitespace */
%%

接着,我们要去定义我们的语法规则(match.y):

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
%{
#include <stdio.h>
#include <string.h>

extern int yylex(void);
extern int yyparse(void);
extern FILE *yyin;
extern int yylineno;

int map[100] = {0};

int yywrap()
{
return 1;
}

void yyerror(const char *s)
{
printf("[error] %s, in %d\n", s, yylineno);
}

int main(int argc, char const *argv[])
{
const char *file = argv[1];
FILE *fp = fopen(file, "r");

if(fp == NULL)
{
printf("cannot open %s\n", file);
return -1;
}

yyin = fp;
yyparse();

return 0;
}
%}

%token T_ECHO T_MATCH T_LEFT_PARENTHESIS T_RIGHT_PARENTHESIS T_NUMBER T_DOUBLE_ARROW T_LEFT_BRACE T_RIGHT_BRACE

%%

statement:
| T_ECHO echo_expr { printf("%d\n", $2); }
;

echo_expr:
expr
{
$$ = $1;
};

expr:
| match_expr { $$ = $1;}
;

match_expr:
T_MATCH T_LEFT_PARENTHESIS T_NUMBER T_RIGHT_PARENTHESIS T_LEFT_BRACE match_arm_list T_RIGHT_BRACE
{
$6 = map[$3];
$$ = $6;
};

match_arm_list:
| match_arm_list match_arm
;

match_arm:
T_NUMBER T_DOUBLE_ARROW T_NUMBER
{
map[$1] = $3;
}

%%

语法规则会稍微难理解一点,我们来解释一下。其中,statement是起始的非终结符,可以推导出我们的echo表达式echo_expr;而我们的echo表达式实际上也是一个表达式,这个表达式可以是我们的match表达式match_exprmatch_expr需要匹配到match等等这些token。但是,我们的匹配列表match_arm_list它不是终结符,我们还可以继续推导,可以发现,实际上match_arm_list是一个递归的,知道推导出match_arm某一项。一旦我们匹配到了match_arm,我们就把keyvalue保存在map里面。

我们来编译一下:

1
2
3
4
5
lex match.l

bison -d match.y

cc -o match lex.yy.c match.tab.c

此时会生成可执行文件match

我们来写一下我们的测试脚本 (match.php):

1
2
3
4
echo match (1) {
1 => 2
2 => 3
}

然后执行脚本:

1
2
./match match.php
2

此时会输出2。如果我们把匹配条件改一下,改成2

1
2
3
4
echo match (2) {
1 => 2
2 => 3
}

将会输出3

1
2
./match match.php
3

符合我们的预期。