이해하는 데 아주 골이 터지는 프로그래밍 언어인 Whirl의 인터프리터입니다. 처음에는 캐릭터 모양으로 만들어 볼까 했으나 하다가 꼬여서 그냥 때려 치고 간단하게 만들었지요... :)
Whirl 홈페이지에도 올라 와 있습니다. :D 이 인터프리터는 GNU LGPL에 따라 배포됩니다.
다음은 현재 최신 버전인, 2005년 6월 17일에 나온 revision 3입니다. 호환성을 살짝 높이고 쓸데 없는 코드를 좀 줄였습니다.
#include/* by Kang Seonghoon <tokigun@gmail.com> */<stdio.h> FILE*f;int P[99999],*d=P,*p=P,U[99999],*u=U,q,s,t,r[2],v[2], w[2];int main(int i,char**I){if(i-2)return!puts("TokigunStu\ dio Whirl Interpreter by Kang Seonghoon <tokigun@gmail.com>" );if(f=**++I-45||1[*I]?fopen(*I,"r"):stdin){while((*d=fgetc( f))>=0 )*d/2-24 ?0 :(*d ++ -= 48) ;for(;p<d;q =!*p++ &&!q)if( *p )v[s ]=(v [s]- r[s ]+ 13)%12;else #define O( n) ;;} else /**/ if( !(v[s]-n)){ {r[s]^= 2; if (q) {t=w [s]; if(s ){; if (0){O(1)t=* u;O(2)*u =t;O (3)t +=*u O(4) t*= *u O(5)t/=*u;O (6)t=0;O( 7) t=t<* u;O( 8) t= t>* u; O(9)t= t==*u;O(10)t=!t;O(11)t=-t;}O(1)return 0;O(2)t=1;O(3)t=0;O(4) t=*u;O(5)*u=t;O(6)p+=t-1;O(7)u+=t;O(8)t=*u&&t;O(9)p+=*u?t-1: 0;O(10)t?printf("%d",*u):scanf("%d",u);O(11)*u=t?putchar(*u) :getchar();}w[s]=t;s=!s;if(p<P)p=P;}}fclose(f);}else printf( "File Not Found: %s\n",*I);return!f;}/*20050617rev3tokigun*/
다음은 2005년 1월 8일에 나온 revision 2입니다. 파일 이름 대신에 -를 지정해서 코드를 표준 입력으로 입력할 수 있습니다.
#include/* by Kang Seonghoon <tokigun@gmail.com> */<stdio.h> FILE*f;int P[99999],*d=P,*p=P,U[99999],*u=U,q,s,t,r[2],v[2], w[2];int main(int i,char**I){if(i-2)return!puts("TokigunStu\ dio Whirl Interpreter by Kang Seonghoon <tokigun@gmail.com>" );if(f=**++I-45||1[*I]?fopen(*I,"r"):stdin){while(~(*d=fgetc (f)))( *d|1)-49 ?0 :(*d ++ -= 48) ;for(;p<d;q =!*p++ &&!q)if( *p )v[s ]=(v [s]- r[s ]+ 13)%12;else #define O( n) ;;} else /**/ if( !(v[s]-n)){ {r[s]^= 2; if (q) {t=w [s]; if(s ){; if (0){O(1)t=* u;O(2)*u =t;O (3)t +=*u O(4) t*= *u O(5)t/=*u;O (6)t=0;O( 7) t=t<* u;O( 8) t= t>* u; O(9)t= t==*u;O(10)t=!t;O(11)t=-t;}O(1)return 0;O(2)t=1;O(3)t=0;O(4) t=*u;O(5)*u=t;O(6)p+=t-1;O(7)u+=t;O(8)t=*u&&!!t;O(9)p+=*u?t- 1:0;O(10)t?printf("%d",*u):scanf("%d",u);O(11)t?putchar(*u): (*u=getchar());}w[s]=t;s=!s;if(p<P)p=P;}}fclose(f);return 0; }return!!printf("File Not Found: %s\n",*I);}/*20050108rev2*/
다음은 2005년 1월 6일에 나온 revision 1입니다.
#include/* by Kang Seonghoon <tokigun@gmail.com> */<stdio.h> FILE*f;int P[99999],*d=P,*p=P,U[99999],*u=U,q,s,t,r[2],v[2], w[2];int main(int i,char**I){if(i-2)return!puts("TokigunStu" "dio Whirl Interpreter by Kang Seonghoon <tokigun@gmail.com" ">");f=fopen(*++I,"r");if(!f)return!!printf(".error: File N" "ot F" "ound -- %s \n", *I ); 42; while(~(*d= fgetc( f)))if(( *d |1)- 49); else {*d ++ -=48;}for(\ fclose( f) ;p <d; q=!( *p++ ||q )){if(*p)v[ s]=(v[s ]- r[ s]+ 13)% 014; else {r[ s] ^=2;if(q){t # define O(n) else if(! (v[s ]-n )) /*2k50106*/ =w[s];if( s) {if(0 );O( 1) t= *u; O( 2)*u=t ;O(3)t+=*u;O(4)t*=*u;O(5)t/=*u;O(6)t=0;O(7)t=t<*u;O(8)t=t>*u ;O(9)t=t==*u;O(10)t=!t;O(11)t=-t;}O(1)return 0;O(2)t=1;O(3)t =0;O(4)t=*u;O(5)*u=t;O(6)p+=t-1;O(7)u+=t;O(8)t=*u&&!!t;O(9)p +=*u?t-1:0;O(10)t?printf("%d",*u):scanf("%d",u);O(11)!t?(*u= getchar()):putchar(*u);w[s]=t;s=!s;if(p<P)p=P;}}}return 0;;}
덤으로, 아래 코드는 Whirl 개발자가 마지막 몇 분의 게으름을 못 참고 만들지 못 했던-_- 바로 그 바이너리-Whirl 코드 변환기입니다.
#include/*tokigun*/<stdio.h> int p,q;int main(int i,char* *j){if(i-2)printf("Usage:\n\ \t%s e < plain > binary\n\t\ %s d < binary > plain\n",*j, *j);else if(**++j==100)while (~(i=getchar()))for(p=128;p; p/=2)putchar(i&p?49:48);else if(**j==101){while(~(i=getc\ har())){if((i|1)==49){p=2*p+ i%2;if(++q>7){putchar(p);p=q =0;}}}q&&putchar(p<<8>>q|255 >>q);}/*20050108*/return 0;}
이 코드는 Microsoft Visual C++ 6.0과 gcc 2.x에서 제대로 컴파일되며, 아마도 웬만한 컴파일러에서는 다 잘 되지 않을까 생각하고 있습니다. gcc의 경우 -Wall 옵션을 줘서 컴파일하면 Warning 하나가 뜨는데 물론 무시해도 관계 없습니다 :) 다음은 gcc를 쓸 경우의 컴파일 방법입니다.
$ gcc whirl.c -o whirl
이 프로그램은 tkbf93과 비슷한 방법으로 사용할 수 있습니다. 인자 하나를 주면 그 파일을 읽어서 실행하고, 아니면 안내 메시지를 출력합니다.
$ whirl TokigunStudio Whirl Interpreter by Kang Seonghoon <tokigun@gmail.com> $ whirl helloworld.txt hello world
revision 2부터는 인자가 -일 경우 표준 입력(stdin)에서 코드를 읽어 오게 되어 있습니다. 단 프로그램 자체가 사용자 입력을 받아 들일 경우 일부 플랫폼에서는 입력값이 EOF로 처리될 수 있습니다. (FreeBSD에서는 동작하지 않고, 윈도우즈에서는 동작하는 것을 확인했습니다.)
$ whirl - 011000001111000011111000001111000011111000001111000 011111000001100100000110011111000111000111100011001 11000000000111110001000111110011001111100010001100 (EOF) 2
2005년 1월 8일에 추가한 유틸리티(whirlutil.c) 역시 같은 방법으로 컴파일하면 됩니다. 그냥 실행할 경우 이 코드는 사용법을 출력하며, 첫 인자가 "e"로 시작하면 표준 입력에서 Whirl 코드를 받아서 바이너리를 출력하고, "d"로 시작하면 거꾸로 표준 입력에서 바이너리를 받아서 Whirl 코드를 출력합니다.
$ whirlutil Usage: whirlutil e < plain > binary whirlutil d < binary > plain $ whirlutil encode 01100001010110010110001001100001011000100111010001110101 (EOF) aYbabtu $ whirlutil decode aYbabtu (EOF) 01100001010110010110001001100001011000100111010001110101
바이너리를 출력할 때 Whirl 코드의 길이가 8의 배수가 아니면 8의 배수가 되도록 뒤에 1 비트들을 붙입니다. 예를 들어서 "010"이 입력되었을 때는 "01011111"이 입력된 것으로 간주합니다. (0 대신 1을 넣는 이유는 Whirl에서 0이 연속으로 들어 있을 경우 원치 않는 명령이 실행될 수 있기 때문입니다.)
실제로는 whirl과 함께 붙어서 다음과 같이 사용합니다.
$ whirlutil e < code.wr > code.wrb $ whirlutil d < code.wrb | whirl -
이 인터프리터는 Whirl 홈페이지에 공개된 공식 인터프리터와 거의 완벽하게 똑같이 동작합니다. (단, 앞에 메시지 뜨는 건 없습니다 -_-;) 또한 공식 인터프리터와 마찬가지로 주석문을 지원하지 않으며, (이건 버그 아닙니다 :p) 프로그램 포인터가 프로그램을 벗어날 경우에 대한 처리도 완전히 되어 있습니다.
단, 프로그램과 메모리 크기는 고정되어 있으므로 큰 프로그램을 실행하기 위해서는 숫자를 바꿔서 재컴파일 해야 합니다. (2005년 6월 현재까지 나온 Whirl 프로그램 중에서 그 만큼 큰 프로그램은 없습니다만...)
이 프로그램은 메모리 포인터가 원래 위치보다 더 앞으로 갈 경우를 따로 처리하지 않습니다.