INTERCAL

난해한 프로그래밍 언어로, 당대에 주로 쓰이던 포트란과 같은 언어들을 패러디한 언어이다. 매뉴얼에 따르면 정식 명칭은 발음이 불가능한 두문자어 이름을 가진 컴퓨터 언어(Compiler Language With No Pronounceable Acronym)라고 하지만 당연히 개드립이다.

1972년에 발표된 아마도 최초의 난해한 프로그래밍 언어라 할 수 있으며, 정말로 개빡치는 문법과 프로그래머의 자존심을 상하게 하는 구조, 매우 제한된 입출력 명령(입력은 숫자를 영어로 읽은 것, 출력은 로마숫자)이라거나, 매뉴얼의 각종 개드립(특수기호의 이름을 이상하게 발음한다거나, 다른 언어 매뉴얼에는 맹장1)이 있으니 자기네는 편도선[tonsil]을 집어 넣었다든지), 그리고 이 모든 난관에도 불과하고 어떻게든 사용이 가능한 튜링 완전한 언어라는(…) 점이 겹쳐서 나름대로 인기 몰이에 성공했다. 게다가 40년이 지난 지금까지도 몇몇 INTERCAL 구현 및 확장들이 여전히 관리되고 있어서, 개발자 중 한 명인 Don Woods가 "이렇게 오랫동안 살아 남을 줄은 몰랐다"2)라고 할 정도이다.

언어

아래에서 설명하는 언어는 최초의 INTERCAL 버전, 즉 INTERCAL-72이며, C-INTERCAL이나 CLC-INTERCAL 등등이 추가한 수많은 명령들은 생략했다. 거기에 대해서 더 알고 싶으면 C-INTERCAL 매뉴얼이나 CLC-INTERCAL 매뉴얼을 뒤져 보시라.

변수와 상수

INTERCAL에는 네 종류의 변수, 즉 16비트 변수(.1, "onespot"), 32비트 변수(:1, "twospot"), 16비트 변수에 대한 배열(,1) 및 32비트 변수에 대한 배열(;1)이 있으며, 각 변수 별로 스택이 하나씩 더 존재한다(STASHRETRIEVE 명령에서 사용). 각 종류 별로 총 65535개(번호가 1부터 65535까지)의 변수 또는 배열이 존재하며, 배열의 크기는 배열에 값을 할당해서 조정할 수 있다(이 경우 기존에 있던 배열 내용은 사라진다). 배열은 굳이 1차원일 필요는 없으며, 배열의 크기를 조정할 때 크기를 여러 개 집어 넣으면(정확히는 BY로 구분해서) 다차원 배열이 만들어진다.

INTERCAL의 상수는 단 한 종류(16비트) 뿐으로, #42와 같이 표시한다. (물론 변수와는 달리 #0는 올바른 상수이다.) 여기에는 두 가지 문제가 있는데, 일단 32비트 상수를 만들려면 복잡한 수식을 써야 한다는 점도 있지만 (이를테면 1234567890을 만들려면 #38412$#10521이라고 해야 한다), INTERCAL-72의 규칙을 그대로 따르면 상수 또한 할당이 가능하다는 점이 문제이다. 즉, INTERCAL의 상수는 사실은 기본적으로 값이 미리 할당되어 있는 16비트 변수와 동일하다. 그러나 이렇게 할 경우 최적화가 매우 어려워지기 때문에, 웬만한 구현체들이 이 기능을 그냥 무시해 버리는 게 보통이다(…).

INTERCAL에는 기본적으로 숫자 말고는 다른 자료형이 없으나, 대부분의 경우 배열을 써서 어떻게든 표현할 수 있다. 예를 들어 C-INTERCAL에서는 문자를 입력받으면 #0부터 #255까지의 문자 코드 또는 파일의 끝(#256)을 돌려 주는 확장이 있다. 참·거짓 값은 많은 다른 언어와는 다르게 보통 #1#2로 표현하는데, 이는 참·거짓에 따라 분기를 시행할 때 코드가 훨씬 간단해지기 때문이다(사실은 #0만 가지고는 곧바로 뭔가를 할 수조차 없다).

수식

INTERCAL은 총 다섯 종류의 연산자를 가지고 있는데, 그 연산자 하나 하나가 황당함의 극치라서 별도의 설명이 필요하다. 역사적인 이유로(보통 문자 집합의 차이 때문에) 연산자를 나타내는 문자가 한 종류가 아닌데, 편의상 여기서는 ASCII 버전을 우선시하도록 한다. 아래에서 <BS>로 나타낸 것은 두 문자를 같은 위치에 표시하기 위한 백스페이스 문자(ASCII 08h)이다.

연산자 다른 표현 설명
a $ b
"mingle"
c<BS>/, ¢ 16비트 숫자 ab를 받아서, a를 홀수번(1,3,5…) 비트로, b를 짝수번(0,2,4…) 비트로 하는 32비트 숫자를 만든다. 이를테면 #2$#3은 비트 1011로 이루어져 있고, 왼쪽·오른쪽에서 순서대로 비트를 끄집어 내면 1101, 즉 #13과 같다. 사실상 32비트 상수를 만드는 유일한 방법.
a ~ b
"select"
(다행히도) 없음 ab를 받아서, b에서 1로 설정된 비트만 a에서 끄집어 내서 순서대로 나열하여 새 숫자를 만든다. 이를테면 #6~#13은 비트 01101101로 이루어져 있고, 뒤에서 1로 설정된 비트만을 앞에서 가져 오면 010이 되므로 #2가 된다. $ 연산자와 합하면 비트 순서를 마음대로 바꾸는 용도로 유용하게 쓸 수 있다.
&x 없음 x를 오른쪽으로 한 비트 회전한 결과를 x'라 할 때, 각각 x AND x', x OR x', 그리고 x XOR x'를 계산한다. 이른바 단항 비트 연산(…). 따라서 두 16비트 숫자를 비트 연산하려면, 일단 둘을 $로 합친 뒤에 이 값을 단항 연산자로 계산하고 매 두번째 비트만 ~ 연산자로 가져 오는 삽질을 해야 한다.
Vx 없음
?x V<BS>-, , ¥

그 밖에 배열에 접근하기 위한 SUB 연산이 있으니, 그냥 :1 SUB #1 하면 된다. (INTERCAL에서 배열 인덱스는 1부터 시작한다.) 다차원 배열은 :1 SUB #1 #2 식으로 접근한다. 굳이 SUB 앞 뒤에 공백을 넣을 필요는 없다만.

보통 대부분의 INTERCAL 수식은 '"를 사용해서 (마치 괄호와 같이) 묶어서 쓴다. 이 두 문자는 열고 닫는 괄호로 모두 쓰이기 때문에(…) 제대로 쓰려면 '로 묶인 수식 안에서는 "로 수식을 묶어야 하고… 뭐 그래야 한다. 참고로 INTERCAL의 이항 연산자에는 우선순위 따위는 없으므로 어쨌든 묶기는 해야 한다(확장으로 비스무리한 걸 지원하는 경우는 있다). 또한 본래 INTERCAL-72에서 단항 연산자는 다른 다항 연산자보다 훨씬 늦게 결합되기 때문에(따라서 &#1$#1#1$#1& 연산자를 적용한다), 안쪽의 수식에 단항 연산자를 적용하려면 #&1이나 :&1과 같이 변수 표시 바로 뒤에(!) 넣어야 한다. '"로 묶은 수식에서는 이런 걱정을 할 필요가 없기 때문에(이를테면 &"&:&1"$#&1은 올바른 수식이다), '"는 매우 빈번하게 나오는 편이다.

문장

INTERCAL은 보통 줄 단위로 파싱되는 언어이며(다만 문장 지시자가 없는 줄은 앞 줄에 함께 붙어서 해석된다), 파싱 자체는 겉보기에는 실행 시간에 일어난다. 따라서 문법 오류는 컴파일 시간이 아니라 실행 시간에 난다(물론 컴파일러가 문법 오류를 미리 판단하고 오류가 나도록 하는 코드로 컴파일했을 수도 있다). 각 줄은 크게 네 부분으로 구성되어 있는데, 줄 번호, 문장 지시자, 명령어, 인자 순서대로 나올 수 있다(인자와 명령어 순서가 다른 NEXT 명령어는 예외이다).

명령 줄 번호 문장 지시자 명령어 인자
DO .1 ← #42 없음 DO CALCULATE .1 ← #42
(42) PLEASE .1 ← #42 (42) PLEASE CALCULATE .1 ← #42
PLEASE DO (42) NEXT 없음 PLEASE DO NEXT (42)
DON'T ABSTAIN FROM CALCULATING 없음 DON'T ABSTAIN FROM CALCULATING
PLEASE DON'T GIVE UP 없음 PLEASE DON'T GIVE UP 없음
PLEASE NOTE: comment here. 없음 PLEASE NOT 파싱 오류

문장 지시자는 DO, PLEASE, PLEASE DO 중 하나이거나, 그 뒤에 N'T 또는 NOT이 붙어 있을 수 있다. 후자는 해당 명령을 실행하지 않을 것을 의미하며, ABSTAIN이나 REINSTATE 같은 명령을 써서 실행되어야 할 명령을 실행되지 않게 하거나, 실행되지 않아야 할 명령을 실행되게 할 수 있다. 맨 마지막 예제에서 볼 수 있듯이 지시자 뒤에 공백 없이 다른 내용이 붙을 경우 파싱 오류이지만, 문장 지시자 자체는 제대로 파싱이 된다3). 또한 전체 프로그램에는 적절한 양의 PLEASE 문이 들어 있어야 하는데, 이는 다른 데는 전혀 영향을 안 미치지만 갯수가 너무 많거나(1/3 이상) 너무 적으면(1/5 이하) 컴파일러가 컴파일을 거절할 수 있다. 까칠하기는

문장 지시자 뒤에는 %1부터 %99까지의 숫자를 써서 해당 문장이 실행될 확률을 조정할 수 있다. 문장 지시자에 이미 NOT이 붙었거나 ABSTAIN이 적용되었을 경우 이 확률은 무시되고 해당 문장은 절대로 실행되지 않는다(물론 다시 REINSTATE를 했다면 확률은 보존된다).

줄 번호는 (베이직 같은 언어와는 다르게) 생략할 수 있으며, 줄 번호가 붙은 문장이 줄 번호 순서대로 실행되지도 않는다. 다만 줄 번호가 붙은 문장은 NEXT 등에서 특정 명령을 가리키는 데 쓸 수 있다. 줄 번호는 (1)부터 (65535)까지 쓸 수 있고, 보통은 (1000)부터 (1999)까지는 시스템 라이브러리를 위해 예약되어 있다. 모든 줄 번호는 프로그램 내에서 유일해야 한다.

명령어는 줄 번호(만약 있다면) 뒤에 바로 오며, 을 포함하는 문장은 암시적으로 CALCULATE 명령을 쓴다고 가정한다. 명령어를 부르는 말(gerund)은 명령어 뒤에 -ing을 붙여 표현한다(CALCULATING 등). 종류가 낱말 두 개가 될 수도 있으며(WRITE IN, GIVE UP과 같이) 이 경우 부르는 말은 WRITING IN 등이 된다4). INTERCAL-72의 모든 명령 목록은 다음과 같다.

명령어 예시 설명
문법 오류
PLEASE NOTE THIS
문법 오류는 올바른 INTERCAL 명령으로, 만약 실행된다면(이를테면 NOT이 없다거나) 오류와 함께 프로그램을 종료한다.
CALCULATE
DO .1 <- #42
DO ,1 <- #3 BY #4
DO ,1 SUB #1 #2 <- #3
지정된 수식을 실행한 뒤 해당하는 변수에 대입한다. 만약 변수가 배열이라면 배열을 초기화하면서 크기를 주어진 수식으로 지정한다(다차원 배열은 BY를 쓴다). 변수 이름은 SUB을 써서 배열 원소로 지정할 수도 있다.
NEXT
DO (1000) NEXT
스택에 해당 줄 번호를 추가한다(유일하게 줄 번호가 명령어 앞에 온다). 스택은 최대 80개까지의 줄 번호만 저장할 수 있다(고로 이걸로는 재귀호출은 무리다).
FORGET
DO FORGET #1
스택에서 주어진 갯수만큼 줄 번호를 뽑은 뒤 버린다. 0개를 뽑거나 스택에 있는 갯수보다 많이 뽑는 것도 가능하다(전자는 아무 일도 하지 않는다).
RESUME
DO RESUME #2
FORGET과 유사하나, 마지막으로 뽑은 줄 번호로 이동한다. 따라서 0개를 뽑거나 스택에 있는 갯수보다 많이 뽑는 건 에러이다. 참·거짓 값에 따라 분기를 수행하는 데 흔히 쓰인다(NEXT를 두 번 한 뒤 RESUME에 해당 값을 넣어서 분기를 수행).
STASH
DO STASH .1 + ;2
주어진 변수 및 배열에 대응하는 스택에 현재 값을 집어넣는다. +로 여러 변수나 배열에 STASH를 적용할 수도 있다(심지어 같은 변수를 한 문장에서 여러 번 적용하는 것도 가능하다).
RETRIEVE
DO RETRIEVE ,3
주어진 변수 및 배열에 대응하는 스택에서 값을 꺼내 와서 현재 값에 덮어 씌운다. 스택이 비어 있으면 오류이다. 문법은 STASH와 동일.
IGNORE
DO IGNORE ,4
주어진 변수 및 배열을 읽기 전용으로 만든다. 이 경우 해당 변수나 배열에 대한 대입은 실행은 되지만 아무 효과도 없으며, 따라서 조건부 대입 등에 유용하다. 문법은 STASH와 동일.
REMEMBER
DO REMEMBER ,4 + ;5
주어진 변수 및 배열을 읽기 및 쓰기 모두 가능하게 한다. 문법은 STASH와 동일.
ABSTAIN
DO ABSTAIN FROM ABSTAINING
DO ABSTAIN FROM (1000)
주어진 명령어로 시작하는 명령들이나, 주어진 줄 번호를 가진 명령을 (NOT이 붙어 있든 없든 상관 없이) 다음부터 실행하지 않도록 한다. 명령어의 목록은 +로 구분한다.
REINSTATE
DO REINSTATE FROM NEXTING
DO REINSTATE FROM (1000)
주어진 명령어로 시작하는 명령들이나, 주어진 줄 번호를 가진 명령을 (NOT가 붙어 있든 없든 상관 없이) 앞으로 항상 실행하도록 한다. 문법은 REINSTATE와 동일. 참고로 DO ABSTAIN FROM REINSTATING이라고 하면 영영 REINSTATE를 못 쓰게 된다(…).
READ OUT
DO READ OUT .1
DO READ OUT ;2 SUB #3
DO READ OUT #4
주어진 수식을 평가한 뒤 출력한다. (READ라는 낱말에 속지 말자!) 출력 포맷은 무려 로마 숫자이며, 숫자가 아닌 출력은 (적어도 INTERCAL-72에서는) 불가능하다(…).
WRITE IN
DO WRITE IN :5
DO WRITE IN ,6 SUB #7 ,8
주어진 변수나 배열 원소에 사용자의 입력을 받아 들인다. 입력 포맷은 숫자를 영어 낱말로 읽은(123이라면 one two three) 것으로, 출력과 마찬가지로 숫자가 아닌 입력은 불가능하다(…). 한 술 더 떠서 최근 구현체들은 영어 말고도 다른 언어들을 지원한다(…).
GIVE UP
DO GIVE UP
프로그램의 실행을 종료한다. ABSTAIN FROM GIVING UP을 쓸 수 없는(문법 오류이다) 흔치 않은 예.

나중의 INTERCAL 확장들에서는 NEXT를 대신하는 COME FROMNEXT FROM 명령이라거나, 좀 더 멀쩡한(그러나 귀찮기는 매한가지인) 문자 단위 입출력 명령이라거나, 멀티스레딩이라거나(이 경우 GIVE UP은 현재 스레드만 종료시킨다), MAYBE 문장 지시자를 사용한 백트래킹 명령이라거나, 심지어 파서를 동적으로 변경시키는 확장까지 온갖 이상한 명령들이 들어가 있지만, 굳이 여기에서 모든 것을 설명할 필요는 없을 것이다.

구현체

현재 가장 널리(!) 쓰이는 구현체는 크게 C-INTERCAL과 CLC-INTERCAL로 구분할 수 있으며, 둘 다 웬만큼 많은 기능들을 지원한다. 참고로 C-INTERCAL의 원래 개발자는 그 유명한 Eric Raymond….

바깥 링크

1) "부록"을 뜻하는 appendix는 "맹장"이라는 뜻도 갖고 있다.
3) 따라서 이 문장은 파싱 오류가 깔끔하게 무시된다. 이 때문에 INTERCAL에서 NOTE를 써서 주석을 표현하는 것은 흔한 일이다.
4) 이 패턴을 따르면 GIVE UP에 대한 부르는 말은 GIVING UP이어야 하지만, INTERCAL-72에서는 예외 조항으로 여기에 대응하는 부르는 말이 없다. 뭐 어쩌라는 건지 알 수 없다.

도쿠위키DokuWiki-custom(rev 9085d92e02)을 씁니다.
마지막 수정 2011-12-24 17:34 | 작성자 lifthrasiir