内容

名称

perlhacktut - 简单的 C 代码补丁创建过程

描述

本文档将带您逐步了解一个简单的补丁示例。

如果您还没有阅读 perlhack,请先阅读!您可能还想阅读 perlsource

完成本文档后,请继续阅读 perlhacktips

简单补丁示例

让我们从头到尾完成一个简单的补丁。

Larry 建议:如果在 pack 操作中,U 是第一个活动的格式(例如,pack "U3C8", @stuff),那么生成的字符串应被视为 UTF-8 编码。

如果您正在使用 Perl 代码库的 git 克隆,您需要为您的更改创建一个分支。这将使创建正确的补丁变得更加简单。有关如何执行此操作的详细信息,请参阅 perlgit

编写补丁

我们如何准备修复这个问题?首先,我们找到相关的代码 - pack 在运行时发生,因此它将位于某个 pp 文件中。果然,pp_pack 位于 pp.c 中。由于我们将要修改此文件,因此让我们将其复制到 pp.c~ 中。

[嗯,在编写本教程时,它位于 pp.c 中。现在它已经与 pp_unpack 分开到自己的文件 pp_pack.c 中了]

现在让我们看一下 pp_pack:我们将一个模式放入 pat 中,然后循环遍历该模式,依次将每个格式字符放入 datum_type 中。然后,对于每个可能的格式字符,我们都会吞掉模式中的其他参数(字段宽度、星号等),并将下一个输入块转换为指定的格式,并将其添加到输出 SV cat 中。

我们如何知道 U 是否是 pat 中的第一个格式?嗯,如果我们有一个指向 pat 开头的指针,那么如果我们看到一个 U,我们可以测试我们是否仍然位于字符串的开头。因此,以下是 pat 的设置位置

STRLEN fromlen;
char *pat = SvPVx(*++MARK, fromlen);
char *patend = pat + fromlen;
I32 len;
I32 datumtype;
SV *fromstr;

我们将在这里添加另一个字符串指针

   STRLEN fromlen;
   char *pat = SvPVx(*++MARK, fromlen);
   char *patend = pat + fromlen;
+  char *patcopy;
   I32 len;
   I32 datumtype;
   SV *fromstr;

在开始循环之前,我们将 patcopy 设置为 pat 的开头

   items = SP - MARK;
   MARK++;
   SvPVCLEAR(cat);
+  patcopy = pat;
   while (pat < patend) {

现在,如果我们看到一个位于字符串开头的 U,我们将为输出 SV cat 打开 UTF8 标志

+  if (datumtype == 'U' && pat==patcopy+1)
+      SvUTF8_on(cat);
   if (datumtype == '#') {
       while (pat < patend && *pat != '\n')
           pat++;

请记住,它必须是 patcopy+1,因为字符串的第一个字符是 U,它已经被吞入 datumtype! 中了

糟糕,我们忘记了一件事:如果模式开头有空格怎么办?pack(" U*", @stuff) 将以 U 作为第一个活动字符,即使它不是模式中的第一个字符。在这种情况下,当我们看到空格时,我们必须将 patcopypat 一起前进

if (isSPACE(datumtype))
    continue;

需要变为

if (isSPACE(datumtype)) {
    patcopy++;
    continue;
}

好了。C 部分完成了。现在,在我们准备好发布此补丁之前,我们必须再做两件事:我们已经改变了 Perl 的行为,因此我们必须记录该更改。我们还必须提供更多回归测试,以确保我们的补丁有效,并且不会在其他地方产生错误。

测试补丁

每个操作符的回归测试都位于t/op/中,因此我们将t/op/pack.t复制到t/op/pack.t~。现在,我们可以将测试添加到末尾。首先,我们将测试U是否确实创建了 Unicode 字符串。

t/op/pack.t 有一个合理的 ok() 函数,但如果它没有,我们可以使用 t/test.pl 中的函数。

require './test.pl';
plan( tests => 159 );

所以,与其这样

print 'not ' unless "1.20.300.4000" eq sprintf "%vd",
                                              pack("U*",1,20,300,4000);
print "ok $test\n"; $test++;

我们可以写出更合理的代码(有关 is() 和其他测试函数的完整说明,请参阅 Test::More)。

is( "1.20.300.4000", sprintf "%vd", pack("U*",1,20,300,4000),
                                      "U* produces Unicode" );

现在,我们将测试我们是否正确地处理了开头空格的问题

is( "1.20.300.4000", sprintf "%vd", pack("  U*",1,20,300,4000),
                                    "  with spaces at the beginning" );

最后,我们将测试如果U不是第一个活动格式,我们不会创建 Unicode 字符串

isnt( v1.20.300.4000, sprintf "%vd", pack("C0U*",1,20,300,4000),
                                      "U* not first isn't Unicode" );

不要忘记更改顶部出现的测试数量,否则自动测试器会感到困惑。它看起来像这样

print "1..156\n";

或者这样

plan( tests => 156 );

现在,我们编译 Perl,并通过测试套件运行它。我们的新测试通过了,万岁!

记录补丁

最后,是文档。在完成文书工作之前,工作永远不会完成,所以让我们描述一下我们刚刚做出的更改。相关的地方是pod/perlfunc.pod;同样,我们做一个副本,然后在pack的描述中插入这段文字

=item *

If the pattern begins with a C<U>, the resulting string will be treated
as UTF-8-encoded Unicode. You can force UTF-8 encoding on in a string
with an initial C<U0>, and the bytes that follow will be interpreted as
Unicode characters. If you don't want this to happen, you can begin
your pattern with C<C0> (or anything else) to force Perl not to UTF-8
encode your string, and then follow this with a C<U*> somewhere in your
pattern.

提交

有关如何提交此补丁的详细信息,请参阅 perlhack

作者

本文档最初由 Nathan Torkington 编写,并由 perl5-porters 邮件列表维护。