====== INTERCAL ====== [[난해한프로그래밍언어]]로, 당대에 주로 쓰이던 [[포트란]]과 같은 언어들을 [[패러디]]한 언어이다. 매뉴얼에 따르면 정식 명칭은 **발음이 불가능한 두문자어 이름을 가진 컴퓨터 언어**(Compiler Language With No Pronounceable Acronym)라고 하지만 당연히 [[개드립]]이다. 1972년에 발표된 아마도 최초의 난해한 프로그래밍 언어라 할 수 있으며, 정말로 개빡치는 문법과 프로그래머의 자존심을 상하게 하는 구조, 매우 제한된 입출력 명령(입력은 숫자를 [[영어]]로 읽은 것, 출력은 [[로마숫자]])이라거나, 매뉴얼의 각종 개드립(특수기호의 이름을 이상하게 발음한다거나, 다른 언어 매뉴얼에는 [[맹장]](("부록"을 뜻하는 appendix는 "맹장"이라는 뜻도 갖고 있다.))이 있으니 자기네는 [[편도선]][tonsil]을 집어 넣었다든지), 그리고 이 모든 난관에도 불과하고 어떻게든 사용이 가능한 [[튜링완전]]한 언어라는(...) 점이 겹쳐서 나름대로 인기 몰이에 성공했다. 게다가 40년이 지난 지금까지도 몇몇 INTERCAL 구현 및 확장들이 여전히 관리되고 있어서, 개발자 중 한 명인 Don Woods가 "이렇게 오랫동안 살아 남을 줄은 몰랐다"(([[http://www.techworld.com.au/article/251892/a-z_programming_languages_intercal/|The A-Z of Programming Languages: INTERCAL]] (Computerworld) ))라고 할 정도이다. ===== 언어 ===== 아래에서 설명하는 언어는 최초의 INTERCAL 버전, 즉 INTERCAL-72이며, C-INTERCAL이나 CLC-INTERCAL 등등이 추가한 수많은 명령들은 생략했다. 거기에 대해서 더 알고 싶으면 [[http://www.catb.org/~esr/intercal/ick.htm|C-INTERCAL 매뉴얼]]이나 [[http://smuggle.intercal.org.uk/manual/|CLC-INTERCAL 매뉴얼]]을 뒤져 보시라. ==== 변수와 상수 ==== INTERCAL에는 네 종류의 변수, 즉 16비트 변수(''.1'', "onespot"), 32비트 변수('':1'', "twospot"), 16비트 변수에 대한 배열('',1'') 및 32비트 변수에 대한 배열('';1'')이 있으며, 각 변수 별로 스택이 하나씩 더 존재한다(''STASH'' 및 ''RETRIEVE'' 명령에서 사용). 각 종류 별로 총 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 버전을 우선시하도록 한다. 아래에서 ''''로 나타낸 것은 두 문자를 같은 위치에 표시하기 위한 백스페이스 문자(ASCII 08h)이다. ^ 연산자 ^ 다른 표현 ^ 설명 ^ | **''//a// $ //b//''**\\ "mingle" | ''c/'', ''¢'' | 16비트 숫자 //a//와 //b//를 받아서, //a//를 홀수번(1,3,5...) 비트로, //b//를 짝수번(0,2,4...) 비트로 하는 32비트 숫자를 만든다. 이를테면 ''#2$#3''은 비트 ''10''과 ''11''로 이루어져 있고, 왼쪽·오른쪽에서 순서대로 비트를 끄집어 내면 ''1101'', 즉 ''#13''과 같다. 사실상 32비트 상수를 만드는 유일한 방법. | | **''//a// ~ //b//''**\\ "select" | (다행히도) 없음 | //a//와 //b//를 받아서, //b//에서 1로 설정된 비트만 //a//에서 끄집어 내서 순서대로 나열하여 새 숫자를 만든다. 이를테면 ''#6~#13''은 비트 ''0110''과 ''1101''로 이루어져 있고, 뒤에서 ''1''로 설정된 비트만을 앞에서 가져 오면 ''010''이 되므로 ''#2''가 된다. ''$'' 연산자와 합하면 비트 순서를 마음대로 바꾸는 용도로 유용하게 쓸 수 있다. | | **''&//x//''** | 없음 | //x//를 오른쪽으로 한 비트 회전한 결과를 //x'//라 할 때, 각각 ''//x// [[비트AND]] //x'//'', ''//x// [[비트OR]] //x'//'', 그리고 ''//x// [[비트XOR]] //x'//''를 계산한다. 이른바 **단항 [[비트연산]]**(...). 따라서 두 16비트 숫자를 비트 연산하려면, 일단 둘을 ''$''로 합친 뒤에 이 값을 단항 연산자로 계산하고 매 두번째 비트만 ''~'' 연산자로 가져 오는 삽질을 해야 한다. | | **''V//x//''** | 없음 | ::: | | **''?//x//''** | ''V-'', ''∀'', ''¥'' | ::: | 그 밖에 배열에 접근하기 위한 ''SUB'' 연산이 있으니, 그냥 '':1 SUB #1'' 하면 된다. (INTERCAL에서 배열 인덱스는 1부터 시작한다.) 다차원 배열은 '':1 SUB #1 #2'' 식으로 접근한다. 굳이 ''SUB'' 앞 뒤에 공백을 넣을 필요는 없다만. 보통 대부분의 INTERCAL 수식은 ''%%'%%'' 및 ''"''를 사용해서 (마치 [[괄호]]와 같이) 묶어서 쓴다. 이 두 문자는 열고 닫는 괄호로 모두 쓰이기 때문에(...) 제대로 쓰려면 ''%%'%%''로 묶인 수식 안에서는 ''"''로 수식을 묶어야 하고... 뭐 그래야 한다. 참고로 INTERCAL의 이항 연산자에는 우선순위 따위는 없으므로 어쨌든 묶기는 해야 한다(확장으로 비스무리한 걸 지원하는 경우는 있다). 또한 본래 INTERCAL-72에서 단항 연산자는 다른 다항 연산자보다 훨씬 늦게 결합되기 때문에(따라서 ''$#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'' 같은 명령을 써서 실행되어야 할 명령을 실행되지 않게 하거나, 실행되지 않아야 할 명령을 실행되게 할 수 있다. 맨 마지막 예제에서 볼 수 있듯이 지시자 뒤에 공백 없이 다른 내용이 붙을 경우 파싱 오류이지만, 문장 지시자 자체는 제대로 파싱이 된다((따라서 이 문장은 파싱 오류가 깔끔하게 무시된다. 이 때문에 INTERCAL에서 ''NOTE''를 써서 주석을 표현하는 것은 흔한 일이다.)). 또한 전체 프로그램에는 **적절한** 양의 ''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'' 등이 된다((이 패턴을 따르면 ''GIVE UP''에 대한 부르는 말은 ''GIVING UP''이어야 하지만, INTERCAL-72에서는 예외 조항으로 여기에 대응하는 부르는 말이 없다. 뭐 어쩌라는 건지 알 수 없다.)). 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 FROM'' 및 ''NEXT FROM'' 명령이라거나, 좀 더 멀쩡한(그러나 귀찮기는 매한가지인) 문자 단위 입출력 명령이라거나, [[멀티스레딩]]이라거나(이 경우 ''GIVE UP''은 현재 스레드만 종료시킨다), ''MAYBE'' 문장 지시자를 사용한 [[백트래킹]] 명령이라거나, 심지어 파서를 동적으로 변경시키는 확장까지 온갖 이상한 명령들이 들어가 있지만, 굳이 여기에서 모든 것을 설명할 필요는 없을 것이다. ===== 구현체 ===== 현재 가장 널리(!) 쓰이는 구현체는 크게 C-INTERCAL과 CLC-INTERCAL로 구분할 수 있으며, 둘 다 웬만큼 많은 기능들을 지원한다. 참고로 C-INTERCAL의 원래 개발자는 그 유명한 [[Eric Raymond]].... ===== 바깥 링크 ===== * [[http://esoteric.voxelperfect.net/wiki/INTERCAL|INTERCAL]] ([[난해한프로그래밍언어위키]]) * [[http://c.intercal.org.uk/|C-INTERCAL]] * [[http://clc.intercal.org.uk/|CLC-INTERCAL]] * [[http://cadie.googlecode.com/svn/trunk/INTERCAL-style-guide.html|구글 INTERCAL 스타일 가이드]] (물론 [[만우절]] 장난이었다.) * [[Donald Knuth]] 옹의 [[http://www-cs-faculty.stanford.edu/~uno/programs/tpk.i|INTERCAL 프로그램]] (...) {{tag>난해한프로그래밍언어}}