我们现在来实现一下PHP8
的match
语法。大概形如:
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 [ \t]+ %%
|
接着,我们要去定义我们的语法规则(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_expr
;match_expr
需要匹配到match
、(
等等这些token
。但是,我们的匹配列表match_arm_list
它不是终结符,我们还可以继续推导,可以发现,实际上match_arm_list
是一个递归的,知道推导出match_arm
某一项。一旦我们匹配到了match_arm
,我们就把key
和value
保存在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 }
|
然后执行脚本:
此时会输出2
。如果我们把匹配条件改一下,改成2
:
1 2 3 4
| echo match (2) { 1 => 2 2 => 3 }
|
将会输出3
:
符合我们的预期。