그림으로 프로그래밍을 한다는 Piet 언어가 너무 해보고 싶어서 한번 번역해본 글이다. 사실은 앞뒤가 바뀐 것이 원문을 읽다가 도대체 무슨 말인지 몰라 번역하고 만 것이지만.. 뭐 상관없다. 번역을 하며 여러가지 얻은 게 많은 것 같다.
아래 글이 너무 어렵다면 짧게 요약한 백준코딩 페이지를 먼저 읽어보는게 유용하다.
아래 글이 너무 어렵다면 짧게 요약한 백준코딩 페이지를 먼저 읽어보는게 유용하다.
개요
Piet은 추상화 그림처럼 보이는 프로그래밍 언어입니다. 기하 추상화를 개척한 예술가인 Piet Mondrian의 이름을 따서 지었습니다. 원래는 이름을 Mondrian으로 하려 했지만, 누군가 이미 평범한 스크립트 언어의 이름으로 선점을 하였습니다. 아마 모두가 난해한 언어의 개발자가 되기는 어려운 것이었을까요.
빨강, 파랑 그리고 노랑의 구성
1921, 피트 몬드리안
|
노트:
- 저는 Piet 사양을 오랜 시간 전에 작성하였고, 소규모 Piet 프로그래머 커뮤니티가 작성한 프로그램, 인터프리터, IDE, 심지어 컴파일러와 함께 언어는 언어 나름대로 발전하였습니다. 저는 “공식적”인 인터프리터를 작성하지 않았고, 가끔 다른 인터프리터는 해석을 달리하는 경우가 있습니다.
- 여태껏 사양에 대한 물음에 정확한 대답보다는 “당신의 생각이 가장 적절합니다”라고 답변하곤 하였습니다. 그리하여 약간씩 다른 버전들이 나오게 되었습니다. 이제 몇 년간 받았던 질문의 일부를 명확히 하기 위해 몇몇 추가 사항들을 명시하였습니다. 제가 바라던 바와 같이 그런 구현들이 적절하며, 대부분이 추가 사항도 이미 따르고 있을 테지만, 몇몇은 그러지 않을 가능성도 있습니다. Caveat emptor; 구매자는 조심하라
- 몇몇 분들이 여러 대회에서 Piet으로 퍼즐을 만드는 걸 좋아합니다. 컴퓨터 코딩에 대한 이해력이 어느 정도 있다면 이 페이지와 몇몇 링크가 그런 퍼즐을 풀 수 있게 도와줄 수는 있습니다. 만약 이해가 되지 않는다거나 너무 어렵다면, 컴퓨터 코딩을 잘 할 것 같은 친구에게 부탁하는 걸 추천합니다. 저에게 메일로 도움을 요청하지 말아 주세요. 그런 퍼즐이 풀리도록 최선을 다하지만, 저는 이런 모두를 도와줄 시간이 없습니다.
코딩 원칙
- 프로그램 코드는 추상화의 형태를 지녀야 한다.
언어 개념
색상
#FFC0C0
밝은 빨강 |
#FFFFC0
밝은 노랑 |
#C0FFC0
밝은 초록 |
#C0FFFF
밝은 청록 |
#C0C0FF
밝은 파랑 |
#FFC0FF
밝은 자홍 |
#FF0000
빨강 |
#FFFF00
노랑 |
#00FF00
초록 |
#00FFFF
청록 |
#0000FF
파랑 |
#FF00FF
자홍 |
#C00000
어두운 빨강 |
#C0C000
어두운 노랑 |
#00C000
어두운 초록 |
#00C0C0
어두운 청록 |
#0000C0
어두운 파랑 |
#C000C0
어두운 자홍 |
#FFFFFF 흰색
|
#000000 검정
|
Piet은 20가지의 다른 색을 사용합니다. 위 표를 참조해 주세요. 첫 세 줄의 18가지 색은 다음의 두 가지 방법으로 순환적 연관이 되어 있습니다.
- 색조 순환 : 빨강 -> 노랑 -> 초록 -> 청록 -> 파랑 -> 자홍 -> 빨강
- 밝기 순환 : 밝음 -> 보통 -> 어두움 -> 밝음
“밝음”이 “어두움”보다 한 단계 어두워진다는 점에 주목해주세요. 그의 반대도 성립합니다.
주황이나 갈색과 같은 추가적인 색도 사용될 수 있지만, 구현 방식에 따라 효과가 달라질 수 있습니다. 가장 간단한 경우로는 인터프리터가 비표준 색을 흰색으로 처리하여, 흰색이 쓰이는 곳의 아무데나 자유롭게 사용할 수 있게 되는 경우가 되겠습니다.
(비표준 색이 검정색으로 처리될 가능성도 있습니다.)
코델
Piet 코드는 위의 색이 모아져서 만들어진 이미지의 방식을 취합니다. 각 픽셀의 색은 언어에 매우 중요하므로, 자세한 보기를 위해 확대 보기를 지원하는 프로그램이 흔합니다. 그런 프로그램에서 확대된 이미지의 픽셀과 혼란이 발생하지 않기 위해, 한 픽셀 만큼의 코드를 “코델”이라 합니다. 한 “코델”은 그런 픽셀이 여럿 모여 형성될 수 있습니다.
색 블록
Piet 코드의 기본 단위는 색 블록입니다. 색 블록은 같은 색의 이어진 코델을 뜻하며, 다른 색과의 경계나 이미지의 가장자리에 의해 구분됩니다. 대각선으로 이어진 코델은 한 블록으로 취급하지 않습니다. 색 블록은 어떤 형태든 가능하며, 하나의 색 블록 안에 다른 색의 “구멍”이 뚫려져 있을 수도 있습니다. 이 경우의 구멍은 주위의 블록으로 간주하지 않습니다.
스택
스택은 개념상 무한히 깊지만, 구현 방식에 따라 최대 스택 깊이가 정해질 수 있습니다. 만약 이 스택이 오버플로우 할 시에는 런타임 에러로 처리하여야 하며, 이를 다루는 방식은 구현 방식에 따라 다릅니다.
프로그램 실행
Piet 언어의 인터프리터는 코드의 좌상단 코델이 속한 색 블록부터 실행을 시작합니다. 인터프리터는 초기에 오른쪽을 가리키고 있는 방향 포인터(DP)를 사용합니다. DP는 왼쪽, 오른쪽, 아래, 위 방향을 가리킬 수 있습니다. 또한 인터프리터는 초기에 왼쪽을 가리키고 있는 코델 선택자(CC)를 사용합니다. CC는 왼쪽이나 오른쪽을 가리킬 수 있습니다. DP와 CC의 방향은 코드를 실행함에 따라 자주 방향이 바뀔 것입니다.
코드가 실행됨에 따라, 인터프리터는 다음의 규칙에 따라 색 블록을 이동합니다.
- 현재 색 블록의 모서리 찾기. 이때 DP가 가리키는 방향 쪽으로 가장 먼 것을 찾는다. (블록이 복잡할 모양일 때는 모서리가 이어지지 않을 수도 있습니다.)
- 찾은 모서리에 있는 현재 색 블록 코델 찾기. 이때 DP의 이동 방향에 대한 CC의 방향 쪽으로 가장 먼 것을 찾는다. (코드 위에 서서 DP의 방향으로 걷고 있다고 생각해 볼 수 있습니다. 아래 표를 참조하세요.)
- 방금 찾은 코델에서 DP가 가리키는 방향의 코델이 속한 색 블록으로 마침내 이동.
인터프리터는 코드가 종료될 때 까지 이를 계속 반복합니다.
DP
|
CC
|
선택되는 코델
|
오른쪽
|
왼쪽
|
가장 위쪽
|
오른쪽
|
가장 아래쪽
| |
아래쪽
|
왼쪽
|
가장 오른쪽
|
오른쪽
|
가장 왼쪽
| |
왼쪽
|
왼쪽
|
가장 아래쪽
|
오른쪽
|
가장 위쪽
| |
위쪽
|
왼쪽
|
가장 왼쪽
|
오른쪽
|
가장 오른쪽
|
문법 소자
숫자
Piet 코드에서 검은색이나 흰색이 아닌 색 블록은 그 코델 갯수 만큼의 정수를 나타냅니다. 여기서 연산자를 사용하지 않는 한 양의 정수가 아닌 정수는 표현할 수 없습니다. 인터프리터가 숫자를 마주쳤다고 하여 반드시 해야 할 일은 없습니다. 특히, 숫자는 스택에 자동으로 넣어지지도 않습니다. 이를 실행하는 명령어는 따로 있습니다. (아래 참조)
정수의 최대 크기는 개념상 무한하지만, 구현 방식에 따라 최대 크기가 정해질 수 있습니다. 정수 오버플로우는 런타임 에러이며, 구현 방식에 따라 처리가 달라집니다.
검정 블록과 모서리
검정색 블록과 코드의 경계는 실행 흐름을 제한합니다. Piet 인터프리터가 검정 블록이나 코드의 경계를 넘어 이동하려 하면, 이동이 취소되고 CC의 방향이 반대로 바뀝니다. 그리고 나서 인터프리터는 현재 블록에서 다시 이동을 시도합니다. 또다시 이동이 실패하면, DP가 시계방향으로 한번 돌아갑니다. 이와 같이 CC와 DP의 방향이 번갈아 바뀌며 시도를 반복합니다. 8번째의 시도 끝에도 현재 블록을 벗어나지 못하였다면, 그곳에서 나갈 방법이 없으며 이에 코드가 중지됩니다.
흰색 블록
흰색 블록은 인터프리터가 방해받지 않고 지나가는 “자유”구역입니다. 색 블록에서 흰색 영역으로 이동했을 때, 인터프리터는 DP의 방향으로 흰색이 아닌 색 블록이 나올 때 까지 흰색 코델 사이를 “슬라이딩”해 나갑니다. 인터프리터가 검정 블록이나 코드의 경계로 슬라이딩 하였다면 이는 방해받았다고 간주되며(위 참조), 다른 경우는 마주친 색 블록으로 이동해 나가게 됩니다. 흰색 블록을 슬라이딩하여 새로운 색 블록에 도착하는 것은 명령을 실행시키지 않습니다.(아래 참조) 이 방식으로, 흰색 블록은 명령을 실행하지 않으면서 현재 색을 바꾸는 데 사용될 수 있으며, 이는 반복문을 코딩하는 데 매우 유용합니다.
흰색 블록을 슬라이딩해 갈 때는 색이 있는 블록이나 모서리로 이동할 때 까지 인터프리터가 직선으로 이동하게 됩니다. 위에서 설명하였던, 인터프리터가 흰색이 아닌 블록에서 나오는 방법을 사용하지 않습니다.
인터프리터가 흰색 블록을 슬라이딩하다 검은색 블록이나 코드의 경계를 만났을 때에 정확히 무슨 일이 일어나는지는 원본 설명에 포함되어 있지 않았습니다. 위의 텍스트를 문자 그대로 받아들인 저의 해석에 따르면:
- 인터프리터가 직선으로 흰색 블록을 “슬라이딩”해 간다.
- 만약 방해를 받았다면, CC의 방향이 반대로 바뀐다. 이는 인터프리터가 갈려 하는 방향에 영향을 주지 않으므로, DP가 시계방향으로 즉시 돌아간다. (역자 : 인터프리터에 따라 마지막에 있던 코델에서 다시 슬라이딩하는 경우도 있음)
- 이제 인터프리터가 현재의 흰색 코델에서 새로운 방향 선택자의 방향으로 슬라이딩하기 시작한다. 이는 색 블록이나 또 다른 방해를 받을 때 까지 지속된다.
- 인터프리터가 흰색 블록 안에서 방해를 받을 때 마다, CC의 방향을 바꾸고 DP를 시계방향으로 돌려가며 다시 슬라이딩을 시도한다. 이 작업은 인터프리터가 색 블록으로 이동하거나(이 때에는 실행이 이어진다), 제자리걸음을 하는 상황이 일어날 때 까지 반복된다. 흰색 블록 안에서만 제자리걸음을 한다면, 흰색 블록 밖으로 나갈 길이 없음을 의미하고 코드의 실행은 중지되어야 한다.
명령어
명령어는 인터프리터가 색 블록에서 다른 색 블록으로 이동해 나가며 발생하는 색의 차이에 따라 정의됩니다. 오른쪽 표와 같이, 각 이동마다 발생하는 색조와 밝기 차이가 실행될 명령을 결정합니다. 이때, 색조와 밝기 차이는 처음에 설명한 순환 순서에 따라 돌아가며 계산합니다. 흰색 블록을 슬라이딩하다 발생한 이동에서는 명령을 실행하지 않습니다. 각 명령어는 아래에 설명되어 있습니다.
색조 변화
|
밝기 변화(어두워짐)
| ||
0
|
1
|
2
| |
0
|
넣기
|
없애기
| |
1
|
더하기
|
빼기
|
곱하기
|
2
|
나누기
|
나머지
|
반전
|
3
|
비교
|
포인터
|
전환
|
4
|
복제
|
굴리기
|
입력(숫자)
|
5
|
입력(문자)
|
출력(숫자)
|
출력(문자)
|
- 넣기(push) : 이전 블록의 값을 스택에 넣습니다. 색 블록의 값은 자동으로 스택에 넣어지지 않으며, 반드시 이 명령을 실행해야 한다는 점을 잊지 말아야 합니다.
- 없애기(pop) : 스택의 최상위 값을 지우고, 없앱니다.
- 더하기(add) : 스택의 최상위 값 두 개를 지우고, 둘을 더한 다음, 그 결과를 스택에 넣습니다.
- 빼기(subtract) : 스택의 최상위 값 두 개를 지우고, 두 번째 최상위 값에서 첫 번째 최상위 값을 뺀 후, 그 결과를 스택에 넣습니다.
- 곱하기(multiply) : 스택의 최상위 값 두 개를 지우고, 둘을 곱한 다음, 그 결과를 스택에 넣습니다.
- 나누기(divide) : 스택의 최상위 값 두 개를 지우고, 두 번째 최상위 값을 첫 번째 최상위 값으로 정수 나눗셈을 한 다음, 그 결과를 스택에 넣습니다. 만약 0으로 나누는 상황이 발생한다면 구현 방식에 따라 오류를 처리합니다. 그러나 단순히 오류를 무시하는 방법이 추천됩니다.
- 나머지(mod) : 스택의 최상위 값 두 개를 지우고, 두 번째 최상위 값을 첫 번째 최상위 값으로 나눈 나머지를 구한 다음, 그 결과를 스택에 넣습니다. 결과는 나누는 수(첫 번째 최상위 값)와 같은 부호로 처리됩니다. 만약 0으로 나누는 상황이 발생한다면 구현 방식에 따라 오류를 처리합니다. 그러나 단순히 오류를 무시하는 방법이 추천됩니다.(아래 주의문 참조)
- 반전(not) : 스택의 최상위 값이 0이면 1로, 그 외에는 0으로 그 값을 대체합니다.
- 비교(greater) : 스택의 최상위 값 두 개를 지우고, 두 번째 최상위 값이 첫 번째 최상위 값보다 크면 1을, 아니면 0을 스택에 넣습니다.
- 포인터(pointer) : 스택의 최상위 값을 지우고, DP를 그 값 만큼 시계방향으로 돌립니다. (음수일 경우는 반시계방향)
- 전환(switch) : 스택의 최상위 값을 지우고, CC의 방향을 그 값 만큼 바꿉니다. (음수일 경우는 절댓값 적용)
- 복제(duplicate) : 최상위 값을 복제하여 스택에 넣습니다.
- 굴리기(roll) : 스택의 최상위 값 두 개를 지우고, 스택에 남아있는 나머지 값들을 두 번째 최상위 값 만큼의 깊이와 첫 번째 최상위 값 만큼의 횟수로 “굴리기”를 실행합니다. n만큼의 깊이로 한번 굴리는 작업은 최상위 값을 n칸 만큼 끌어내리고, 그 위에 (밀린)값들을 한 칸씩 올리는 작업으로 정의됩니다. 횟수가 음수이면 반대 방향으로 굴리기를 실행합니다. 깊이가 음수인 명령은 오류이며 무시됩니다. 구현 방식에 따른 스택 깊이보다 더 깊게 굴리기를 실행한다면, 구현 방식에 따라 오류를 처리합니다. 그러나 단순히 오류를 무시하는 방법이 추천됩니다.
- 입력(in) : STDIN으로부터 구현 방식에 따라 문자나 수의 형태로 값을 읽어들이고, 스택에 값을 넣습니다. STDIN에서 대기중인 입력이 없다면, 이는 오류로 처리하며 명령은 무시됩니다. 정수 읽기에서 정수를 받지 못했다면, 이는 오류이며 명령은 무시됩니다.
- 출력(out) : 스택의 최상위 값을 지우고, STDOUT으로 구현 방식에 따라 문자나 수의 형태로 이를 출력합니다.
실행될 수 없는 명령은(스택에 충분한 값들이 없음에도 없애기(pop) 명령의 실행 등) 단순히 무시되며, 다음 명령으로 넘어갑니다.
나머지(mod) 명령에 대한 주의문: 원래의 Piet 설명에는 나누는 수(첫 번째 최상위 값)가 음수일 때의 결과가 명확히 설명 되어 있지 않았습니다. 저는 모두가 (p mod q) 의 결과는 ((p + Nq) mod q)와 같은 것이라 생각한다고 가정하였습니다.
- 5 mod 3 = 2
- 2 mod 3 = 2
- -1 mod 3 = 2
- -4 mod 3 = 2
예제와 리소스
Patreon에서 후원하기 (원본 저자)
2018년 09월 27일 04:00:52 PDT에 마지막으로 업데이트됨
Copyright © 1990-2019, David Morgan-Mar. dmm@dangermouse.net
번역 yclee126@gmail.com 2019/08/05
이렇게까지 번역하고 나서야 (출력)프로그램 몇 가지를 작성해볼 수 있었다.
특히 한글 유니코드 한글자가 3바이트여서 내 이니셜을 출력하는 만큼의 블럭이 들어갔다. 사용한 IDE는 Gabrielle의 MasterPiets이다. 코드를 작성할 때에 굳이 색 차이가 얼마나 나는지 계속 확인해볼 필요도 없고 자체 디버거까지 있다. 한가지 흠은 PNG 내보내기 기능이 동작하지 않는다는 점이다. 이전에는 PietDev를 사용했으나 여러가지로 불편하여 결국 옮겨왔다.
이렇게까지 번역하고 나서야 (출력)프로그램 몇 가지를 작성해볼 수 있었다.
![]() |
이니셜 "LYC"를 출력하는 프로그램 |
![]() |
한글 유니코드 "가"를 출력하는 프로그램 |
특히 한글 유니코드 한글자가 3바이트여서 내 이니셜을 출력하는 만큼의 블럭이 들어갔다. 사용한 IDE는 Gabrielle의 MasterPiets이다. 코드를 작성할 때에 굳이 색 차이가 얼마나 나는지 계속 확인해볼 필요도 없고 자체 디버거까지 있다. 한가지 흠은 PNG 내보내기 기능이 동작하지 않는다는 점이다. 이전에는 PietDev를 사용했으나 여러가지로 불편하여 결국 옮겨왔다.
그러고 보니 이 번역도 8월 초에 써놓고 3달이 지나서야 조심스레 블로그에 올린다. 처음으로 크게 번역한 글인 만큼 올리기가 무서웠지만 왜 그랬는지 모르겠다.
댓글 없음:
댓글 쓰기