怎样为Perl 5增加特性
Submitted by editor on Thu, 06/26/2008 - 13:23.
in
怎样为Perl 5增加特性
by chromatic in Technical
作者:chromatic in Technical 翻译:jianxin
昨天的文章《 How to Patch Perl 5 》解释了怎样为虚拟机上的动态语言增加特性的大体的方法。现在,是时候该讨论一些技术细节了。
TODO建议实行可能是和翻译...到Perl 5代码“未生效”一样简单。那是非常可能的通过使用一个源过滤器,比如说 Filter::Simple(考虑一下Perl 5的宏),但是我想要一个更好的。
就如同我昨天建议个那样,有两个步骤。首先是确保Perl的剖析器意识到了陈述模式的语法构造,第二步是生成正确的操作来执行想要的行为。意识到一个语法构造是lexer的工作,这里是这个补丁的一部分:
diff --git a/toke.c b/toke.c
index 431938f..fecbf9f 100644
--- a/toke.c
+++ b/toke.c
@@ -368,6 +368,7 @@ static struct debug_tokens {
{ WHEN, TOKENTYPE_IVAL, "WHEN" },
{ WHILE, TOKENTYPE_IVAL, "WHILE" },
{ WORD, TOKENTYPE_OPVAL, "WORD" },
+ { YADAYADA, TOKENTYPE_IVAL, "YADAYADA" },
{ 0, TOKENTYPE_NONE, NULL }
};
@@ -4774,6 +4775,10 @@ Perl_yylex(pTHX)
pl_yylval.ival = 0;
OPERATOR(ASSIGNOP);
case '!':
+ if (PL_expect == XSTATE && s[1] == '!' && s[2] == '!') {
+ s += 3;
+ LOP(OP_DIE,XTERM);
+ }
s++;
{
const char tmp = *s++;
@@ -5025,10 +5030,14 @@ Perl_yylex(pTHX)
AOPERATOR(DORDOR);
}
case '?': /* may either be conditional or pattern */
- if(PL_expect == XOPERATOR) {
+ if (PL_expect == XSTATE && s[1] == '?' && s[2] == '?') {
+ s += 3;
+ LOP(OP_WARN,XTERM);
+ }
+ if (PL_expect == XOPERATOR) {
char tmp = *s++;
if(tmp == '?') {
- OPERATOR('?');
+ OPERATOR('?');
}
else {
tmp = *s++;
@@ -5067,6 +5076,10 @@ Perl_yylex(pTHX)
PL_expect = XSTATE;
goto rightbracket;
}
+ if (PL_expect == XSTATE && s[1] == '.' && s[2] == '.') {
+ s += 3;
+ OPERATOR(YADAYADA);
+ }
if (PL_expect == XOPERATOR || !isDIGIT(s[1])) {
char tmp = *s++;
if (*s == tmp) {
这个lexer是一个陈述机器带有一点对未来的预测。我为lexer增加了一个新的标记传递给剖析器--YADAYADA标记。那不是总是必要的,但是它确实为处理增加了一些自我文档。
就如你所期望的那样,lexer一次浏览程序的源代码的一个字符,并且在它的核心有一个巨大的转换陈述。开始的两块lexing代码分别增加了对!!!和???的支持。这应该会很容易的看到它们寻找一行中的三个感叹号或者问号。 PL_expect允许一定程度的预先准备。这种语法只有在Perl语法期望一个陈述时才会是有效的。
LOP宏令人有一些混乱。这直接与Perl内部怎样表示程序有关。
我之前说过...是等价于“未生效的”;相似的,!!!$some_message;与$some_message;是等价的,???$another_message是警告$another_message;。这意味着这些新的运算符产生的操作树必须与它们等价的形式产生的操作树相同。
这些很容易在Perl 5中发现:
$ perl -MO=Concise -e 'die "Unimplemented"'
6 <@> leave[1 ref] vKP/REFC ->(end)
1 <0> enter ->2
2 <;> nextstate(main 1 -e:1) v ->3
5 <@> die[t1] vK/1 ->6
3 <0> pushmark s ->4
4 <$> const[PV "Unimplemented"] s ->5
-e syntax OK
这对你来说可能看起来有些莫名其妙,但是它是一个逐字的Perl的optree。编号是与执行顺序相关的,并且相等的符号指明操作的类型。最重要的操作是die 操作。开头的@意味着它是一个列表操作,它显然有两个孩子,一个堆栈操作(Perl是一个基于堆栈的虚拟机)和一个常量字符(SvPV类型)。
在标记中的LOP宏产生一个所给类型的列表操作(你可以指出 OP_WARN和 OP_DIE代表什么)并且期待一个接下来的期限--一串字符或者变量或者表达式,它们估测出一个期限。那就是全部了。剩下的剖析过程适当地联合这个分支成为一个操作树,并且每一件事都如期望的那样工作。
那么...块是怎么样的呢?它坚持它自己;它是一个完整的陈述。在这个例子中, lexer接受输入并且产生YADAYADA标记使得剖析器可以适当的处理。这里是剖析器补丁的相应的部分:
diff --git a/perly.y b/perly.y
index ad7b552..22790f9 100644
--- a/perly.y
+++ b/perly.y
@@ -72,7 +72,7 @@
%token FORMAT SUB ANONSUB PACKAGE USE
%token WHILE UNTIL IF UNLESS ELSE ELSIF CONTINUE FOR
%token GIVEN WHEN DEFAULT
-%token LOOPEX DOTDOT
+%token LOOPEX DOTDOT YADAYADA
%token FUNC0 FUNC1 FUNC UNIOP LSTOP
%token RELOP EQOP MULOP ADDOP
%token DOLSHARP DO HASHBRACK NOAMP
@@ -106,7 +106,7 @@
%left ','
%right ASSIGNOP
%right '?' ':'
-%nonassoc DOTDOT
+%nonassoc DOTDOT YADAYADA
%left OROR DORDOR
%left ANDAND
%left BITOROP
@@ -1227,6 +1227,11 @@ term : termbinop
}
| WORD
| listop
+ | YADAYADA
+ {
+ $$ = newLISTOP(OP_DIE, 0, newOP(OP_PUSHMARK, 0),
+ newSVOP(OP_CONST, 0, newSVpvs("Unimplemented")));
+ }
;
/* "my" declarations, with optional attributes */
开始的两个补丁块声明YADAYADA标记是一个有效的标记,没有给它任何特别的结合性。最后的块更加有趣;它是在这个语法中期限规则的一个新的分支。无论在哪里在Perl 5的语法中一个期限是有效的,...就是有效的。
像LOP宏一样,在这里所有必须的是产生一个有效的操作树的分支包含有两个孩子的die操作:一个压栈操作和一个字符串(SvPV类型)。那就是代码所作的。
补丁剩下的部分是测试,文档,生成文件的变化。它使用了18行代码(非常慷慨的空格和括号被作为一行)增加新的三个特性到Perl 5。并不是所有的特性都如此容易的添加,并且我已经确定的写了更好的代码,但这是一个周五晚上的修改任务,并且是一个相对容易个方法来展示一个虚拟机编程语言是怎样在一个解释级别来工作的。
- editor's blog
- Login or register to post comments

