펄 C 코드 패치 만들기 워크스루
펄 C 코드 패치 만들기 워크스루 (perlhacktut)
간단한 C 코드 패치 하나를 처음부터 끝까지 만들어 보는 튜토리얼이에요. 펄 내부(C 소스)를 고치는 작업이 어떻게 진행되는지 흐름을 따라가면서 익힐 수 있어요.
출처: perlhacktut - Walk through the creation of a simple C code patch
개요 (DESCRIPTION)
이 문서는 간단한 패치 예제 하나를 단계별로 안내해요.
아직 perlhack을 읽지 않았다면 먼저 읽고 오세요! perlsource도 읽어 보면 좋아요.
여기까지 왔으면 다음으로 perlhacktips를 확인하세요.
간단한 패치의 예제 (EXAMPLE OF A SIMPLE PATCH)
간단한 패치 하나를 시작부터 끝까지 살펴보아요.
Larry가 제안한 예가 있어요: pack 동안 U가 첫 번째 활성 포맷이라면(예: pack "U3C8", @stuff), 결과 문자열은 UTF-8로 인코딩된 것으로 취급되어야 한다는 거예요.
Perl 저장소의 git 클론으로 작업하고 있다면, 변경 사항을 위한 브랜치를 만들고 싶을 거예요. 이렇게 하면 제대로 된 패치를 만드는 게 훨씬 간단해져요. 방법의 자세한 내용은 perlgit을 보세요.
패치 작성 (Writing the patch)
이 문제를 고치기 위해 어떻게 준비할까요? 먼저 해당 코드를 찾아야 해요. pack은 런타임에 발생하므로 pp 파일 중 하나에 있을 거예요. 역시나 pp_pack은 pp.c에 있어요. 이 파일을 수정할 것이므로 *pp.c~*로 복사해 두죠.
[글쎄요, 이 튜토리얼이 작성됐을 때는 pp.c에 있었지만, 지금은 pp_unpack과 함께 pp_pack.c라는 자체 파일로 분리되었어요.]
이제 pp_pack을 살펴보죠: 패턴을 pat에 넣고, 패턴을 루프하면서 각 포맷 문자를 차례로 datum_type에 담아요. 그리고 각 가능한 포맷 문자에 대해 패턴의 다른 인자(필드 폭, 별표 등)를 삼키고(swallow), 입력의 다음 청크를 지정된 포맷으로 변환해 출력 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++;
문자열의 첫 문자는 datumtype에 삼켜진 U이므로 patcopy+1이어야 한다는 것을 기억하세요!
아차, 한 가지를 잊었어요: 패턴 시작에 공백이 있으면 어떨까요? pack(" U*", @stuff)는 U가 패턴의 첫 번째 것이 아니어도 첫 번째 활성 문자로 갖게 돼요. 이 경우 공백을 볼 때 patcopy를 pat과 함께 전진시켜야 해요:
if (isSPACE(datumtype))
continue;
이렇게 바뀌어야 합니다:
if (isSPACE(datumtype)) {
patcopy++;
continue;
}
좋아요. 이제 C 부분이 끝났어요. 이 패치가 준비되기 전에 추가로 두 가지를 해야 해요: Perl의 동작을 바꿨으니 그 변경을 문서화해야 하고, 패치가 동작하고 다른 곳에서 버그를 만들지 않는지 확인하기 위한 회귀 테스트(regression test)도 더 제공해야 해요.
패치 테스트 (Testing the patch)
각 연산자의 회귀 테스트는 *t/op/*에 있어요. 그래서 t/op/pack.t의 복사본을 *t/op/pack.t~*로 만듭니다. 이제 테스트를 끝에 추가할 수 있어요. 먼저 U가 실제로 유니코드 문자열을 만드는지 테스트할게요.
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가 첫 번째 활성 포맷이 아닐 때 유니코드 문자열을 만들지 않는지 테스트할게요:
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을 컴파일하고 테스트 스위트로 돌려봅니다. 새 테스트가 통과해요, 만세!
패치 문서화 (Documenting the patch)
마지막으로 문서입니다. 서류 작업이 끝나기 전에는 작업이 끝난 게 아니니까, 방금 만든 변경을 설명해 보죠. 관련 위치는 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.
제출 (Submit)
이 패치를 제출하는 방법에 대한 자세한 내용은 perlhack을 보세요.
저자 (AUTHOR)
이 문서는 원래 Nathan Torkington이 작성했으며, perl5-porters 메일링 리스트가 관리해요.
더 알아보기 (Learn more)
perlhack— 펄 C 소스 해킹 안내perlhacktips— 펄 C 코딩 팁perlsource— 펄 소스 트리 구조perlgit— git으로 패치 만드는 방법Test::More— 테스트 함수