==개요== {{{#!wiki style="text-align:center;border:2px solid #00A495;padding: 10px;" 이 문서의 전체 또는 일부는 [[https://gist.github.com/xnuk/d9f883ede568d97caa158255e4b4d069#file-hyeong-md]]에서 가져왔습니다.}}} [[https://gist.github.com/xnuk|xnuk]]이 개발한 [[한글]] 난해한 [[프로그래밍 언어]]이다. ==문법== - `…`(U+2026), `⋯`(U+22EF), `⋮`(U+22EE)는 모두 `.`(U+002E)가 3번 연속으로 나열된 것과 같은 것으로 봅니다. - "한글 음절 문자"는 가(U+AC00) 이상 힣(U+D7A3) 이하의 유니코드 문자들을 의미합니다. ===스택=== - 스택의 모든 원소는 유리수 또는 `NaN`으로 이루어져 있습니다. 분모가 1인 유리수를 편의상 `정수`라고 칭합니다. - `NaN`은 특수한 숫자처럼 취급됩니다. 어떠한 연산을 하더라도 그 연산에 `NaN`이 있을 경우 그 연산의 값은 `NaN`이 됩니다. - 0번 스택은 입력된 문자들의 유니코드 문자값들의 나열입니다. (stdin). 먼저 입력된 문자가 먼저 뽑힙니다. 기본적으로 빈 스택이며 이 스택이 빈 스택일 때 뽑기 연산이 일어날 시 프로그램에서 필요한 만큼 문자 입력을 받아 스택에 저장합니다. 프로그램이 입력값을 더 이상 받을 수 없는 경우 빈 스택에서 뽑기 연산이 일어날 시 `NaN`을 제공합니다. 넣기 연산이나 뽑을 값이 충분할 때의 뽑기 연산은 일반 스택처럼 작동합니다. - 1번 스택으로 넣어지는 값들은 출력됩니다. (stdout). 항상 빈 스택이며, 뽑기 연산이 일어날 경우 프로그램을 정상적으로 종료합니다. - 이 스택에 넣어지는 모든 0보다 크거나 같은 정수들은 그에 해당하는 유니코드 문자로 출력됩니다. - 이 스택에 넣어지는 모든 음의 정수들은 부호를 제외하고 `0`(U+0030) 이상 `9`(U+0039) 이하에 있는 문자들을 사용하여 10진수 숫자로 출력합니다. - 이 스택에 넣어지는 모든 정수가 아닌 유리수들은 자신보다 작은 정수 중 가장 큰 정수를 택하여 위의 과정을 처리합니다. - 이 스택에 `NaN`이 넣어지는 경우 `너무 커엇...`을 출력합니다. - 2번 스택으로 넣어지는 값들은 에러 문자로 출력됩니다. (stderr). 출력 규칙은 1번 스택과 동일합니다. 항상 빈 스택이며, 뽑기 연산이 일어날 경우 프로그램을 비정상적으로 종료합니다. - 3번 스택부터는 특별한 기능을 하지 않는 일반 스택이며, 모두 기본적으로 빈 스택입니다. 프로그램 시작 시 현재 스택은 3번 스택이 됩니다. 빈 스택에서 뽑기 명령을 실행할 경우 `NaN`을 제공합니다. ===명령어=== 명령어는 연속적인 한글 음절 문자들과 뒤따라 나오는 마침표 `.`(U+002E)들로 이루어져 있습니다. 전자의 문자 수를 `글자 수`라고 하고, 후자의 문자 수를 `마침표 개수`라고 합니다. 명령어 끝에 바로 뒤따라 하트, 물음표, 느낌표들이 나올 수 있으며 이것 역시 해당 명령어의 일부입니다. 이는 후술합니다. 프로그램은 이 명령어들의 나열로 되어 있으며, 먼저 나오는 명령어부터 순차적으로 실행됩니다. 더 이상 읽을 명령어가 없을 경우 맨 처음 명령어로 돌아옵니다. 명령어 해석에 대해선 추가적으로 후술합니다. - `형`, `혀엉`, `혀어엉`, `혀어어엉`… : 글자 수와 마침표 개수를 곱한 값을 현재 스택에 저장합니다. - 예를 들어 `혀어엉....`은 `12`를 현재 스택에 넣습니다. - `혀`와 `엉` 사이에 `엉`을 제외한 한글 음절 문자를 추가적으로 넣어 글자 수를 늘릴 수 있습니다. - `항`, `하앙`, `하아앙`, `하아아앙`… : 현재 스택에서 글자 수만큼 뽑아 모두 더하여 마침표 개수에 해당하는 스택에 넣습니다. - 예를 들어 `하아앙....`은 원소 세 개를 뽑아 모두 더한 다음 그 값을 4번 스택에 넣습니다. - `하`와 `앙` 사이에 `앙`을 제외한 한글 음절 문자를 추가적으로 넣어 글자 수를 늘릴 수 있습니다. - `핫`, `하앗`, `하아앗`, `하아아앗`… : 현재 스택에서 글자 수만큼 뽑아 모두 곱하여 마침표 개수에 해당하는 스택에 넣습니다. - 예를 들어 `하아앗....`은 원소 세 개를 뽑아 모두 곱한 다음 그 값을 4번 스택에 넣습니다. - `하`와 `앗` 사이에 `앗`을 제외한 한글 음절 문자를 추가적으로 넣어 글자 수를 늘릴 수 있습니다. - `흣`, `흐읏` , `흐으읏`, `흐으으읏`… : 스택의 위쪽에서 글자 수만큼의 원소들의 부호를 바꾼 후 그 합을 마침표 개수에 해당하는 스택에 넣습니다. - 예로 스택이 `1 0 -3 4`순으로 있고 `4`가 다음에 뽑힐 원소인 상태에서 `흐읏...`을 하면 스택은 `1 0 3 -4`가 되고, 3번 스택에 `-1`을 넣습니다. - `흐`와 `읏` 사이에 `읏`을 제외한 한글 음절 문자를 추가적으로 넣어 글자 수를 늘릴 수 있습니다. - 이 명령은 현재 스택에서 글자 수만큼의 뽑기 동작과 넣기 동작을 수행하는 명령입니다. 글자 수만큼 뽑아 모두 부호를 바꾼 뒤 나중에 뽑은 원소가 먼저 넣어지게 합니다. 그 후 합을 마침표 개수에 해당하는 스택에 넣습니다. - `흡`, `흐읍`, `흐으읍`, `흐으으읍`… : 스택의 위쪽에서 글자 수만큼의 원소들을 역수로 바꾼 후 그 곱을 마침표 개수에 해당하는 스택에 넣습니다. - 예로 스택이 `1 6 (-3/2) (4/7)`순으로 있고 `4/7`가 다음에 뽑힐 원소인 상태에서 `흐으읍...`을 하면 스택은 `1 (1/6) (-2/3) (7/4)`가 되고, 3번 스택에 `-7/36`을 넣습니다. - `0`의 역수는 `NaN`이 됩니다. - `흐`와 `읍` 사이에 `읍`을 제외한 한글 음절 문자를 추가적으로 넣어 글자 수를 늘릴 수 있습니다. - 이 명령은 현재 스택에서 글자 수만큼의 뽑기 동작과 넣기 동작을 수행하는 명령입니다. 글자 수만큼 뽑아 모두 역수로 바꾼 뒤 나중에 뽑은 원소가 먼저 넣어지게 합니다. 그 후 곱을 마침표 개수에 해당하는 스택에 넣습니다. - `흑`, `흐윽`, `흐으윽`, `흐으으윽`… : 현재 스택에서 값을 하나 뽑아, 마침표 개수에 해당하는 스택에 글자 수만큼 복제해서 넣고, 현재 스택에 뽑았던 값을 하나 넣은 뒤, 마침표 개수에 해당하는 스택으로 이동합니다. - 예를 들어 `흐윽....`은 현재 스택 맨 위에 있는 값을 4번 스택에 두 개 넣고 4번 스택으로 이동합니다. ===하트, 물음표, 느낌표=== 명령어 맨 끝에 하트, 물음표 `?`(U+003F), 느낌표 `!`(U+0021) 문자가 여러 개 올 수 있습니다. 이것 역시 명령어의 구성 중 하나이며, 명령어 사이를 오가게 할 수 있습니다. 이 하트, 물음표, 느낌표가 나열된 부분을 `구역`이라고 부르겠습니다. 이 구역은 이전의 명령어 부분이 실행된 후 실행됩니다. 실행될 구역이 비었을 경우 실행하지 않고 다음 명령어로 넘어갑니다. - `♥`, `❤`, `💕`, `💖`, `💗`, `💘`, `💙`, `💚`, `💛`, `💜`, `💝` : **글자 수와 마침표 개수를 곱한 값**과 **하트의 모양**에 해당하는 명령어로 넘어간 뒤 그 위치에서 코드를 차례대로 실행합니다. 해당하는 명령어가 없으면 현재 명령어를 등록한 뒤 코드를 진행합니다. 만약 넘어갈 명령어가 자기 자신을 뜻하는 경우 넘어가지 않습니다. - 명령어는 등록될 수 있습니다. 예로 이전에 등록된 명령어가 없는 상태에서 `흐읏...♥`을 실행할 경우 이 명령어는 `(6, ♥)`에 등록됩니다. 이후 `하앙...♥` 등을 실행하면 이 위치로 돌아옵니다. - 하트는 몇 번째에 위치한 명령어로 가는 것이 아니라, 이미 정해진 명령어로 가는 동작입니다. 예를 들어, `(6, ❤)`에 명령어가 등록되어 있지 않은 경우, `혀엉...❤`은 현재 명령어를 `(6, ❤)`에 등록합니다. 이후 어디서든지 `하앙...❤`을 실행할 경우 `(6, ❤)`에 있는 `혀엉...❤`으로 넘어간 뒤 명령어를 실행하게 됩니다. 이후 다음 명령어로 가면서 순차적으로 프로그램을 실행합니다. - *넘어간다*는 건 코드가 읽고 있던 위치를 바꾼다는 뜻입니다. 해당하는 위치의 명령어로 간 다음, 그 명령어를 실행한 후, 코드 상으로 그 명령어 바로 다음에 있는 명령어들을 순차적으로 실행합니다. - `♡` : 등록된 명령어로 넘어가게 한 명령어 중 가장 최근에 실행된 명령어로 넘어갑니다. 없을 경우 넘어가지 않습니다. - 예로 `형...♥ 혀어엉..♡ 하읏 흑...♥`은 `형...♥` `혀어엉..♡` `하읏` `흑...♥` `형...♥` `혀어엉..♡` `흑...♥` 순으로 실행됩니다. 1. `형...♥`: `(3, ♥)`에 자기 자신을 등록합니다. 2. `혀어엉..♡`: 넘어가게 한 명령어가 아직 없으므로 그대로 진행합니다. 3. `하읏` 4. `흑...♥`: `(3, ♥)`에 `형...♥`이 등록되어 있으므로, 그곳으로 넘어갑니다. 5. `형...♥`: `(3, ♥)`는 자기 자신을 뜻하므로, 넘어가지 않습니다. 6. `혀어엉..♡`: 가장 최근에 넘어가게 한 명령어는 `흑...♥`입니다. 그곳으로 넘어갑니다. 7. `흑...♥`: `(3, ♥)`에 `형...♥`이 등록되어 있으므로, 그곳으로 넘어갑니다. 즉 이 코드는 무한히 실행될 것으로 예상됩니다. - 위의 예제에서 `형...♥` `혀어엉..♡` `하읏` `흑...♥` `형...♥` `혀어엉..♡` 까지 실행된 이후엔, `혀어엉..♡`은 특정 명령어로 넘어가는 명령어지만, `혀어엉..♡`은 등록되지 못하는 명령어이므로 `♡`로 인해 이 위치로 곧바로 넘어오지 못합니다. - `?`와 `!`는 구역을 나눕니다. 예로 `구역`이 `abcd?efgh`였다면, 이는 `abcd` 구역과 `efgh` 구역을 `?`로 연결시킨 것으로 해석됩니다. `?`와 `!`는 이 두 구역 중 한 구역을 선택하는 동작을 하게 됩니다. 구역이 선택되면, 그 구역을 해석한 뒤 실행합니다. - `?` : 현재 스택에서 값을 하나 뽑아 글자 수와 마침표 개수를 곱한 값보다 작으면 물음표보다 앞부분 구역을 실행하고, 그렇지 않거나 `NaN`일 경우 뒷부분 구역을 실행합니다. 뽑은 값은 버려집니다. - 예를 들어, `혀엉...💗?💕`는 `💕`를 실행합니다. 1. `혀엉...`으로 스택에 `6`을 집어넣습니다. 2. 스택에서 값을 뽑습니다. 아까 `6`을 넣었으므로 값은 `6`입니다. 3. `혀엉...`에서 글자 수와 마침표 개수를 곱하면 `6`이 됩니다. 따라서 오른쪽 부분인 `💕`를 실행합니다. - 구역 내 물음표가 여러 개일 경우, 맨 왼쪽의 물음표부터 계산됩니다. `a?b?c`는 `a`, `?`, `b?c`로 나뉩니다. - 물음표는 구역 안에서 가장 먼저 계산됩니다. `a!b?c!d`는 `a!b`와 `c!d`를 `?`로 연결시킨 것으로 해석됩니다. - `!` : 현재 스택에서 값을 하나 뽑아 글자 수와 마침표 개수를 곱한 값과 같으면 느낌표보다 앞부분 구역을 실행하고, 그렇지 않거나 `NaN`일 경우 뒷부분 구역을 실행합니다. 뽑은 값은 버려집니다. - 예를 들어, `혀엉...💗!💕`는 `💗`를 실행합니다. 1. `혀엉...`으로 스택에 `6`을 집어넣습니다. 2. 스택에서 값을 뽑습니다. 아까 `6`을 넣었으므로 값은 `6`입니다. 3. `혀엉...`에서 글자 수와 마침표 개수를 곱하면 `6`이 됩니다. 따라서 왼쪽 부분인 `💗`를 실행합니다. - 구역 내 느낌표가 여러 개일 경우, 맨 왼쪽의 느낌표부터 계산됩니다. `a!b!c`는 `a`, `!`, `b!c`로 나뉩니다. - 느낌표는 구역 안에서 물음표 다음으로 먼저 계산됩니다. ===명령어 해석=== 명령어 문단에서 알 수 있듯이, 명령어는 `형항핫흣흡흑혀하흐`의 총 9글자 중 하나로 시작됩니다. 아래는 한 개의 명령어를 해석하는 방법을 서술합니다. [한글 글자 해석 단계]부터 시작됩니다. 예상하지 못한 문자나 해석할 수 없는 문자는 모두 무시합니다. 문자가 해석이 된 후엔 바로 다음 문자로 이동합니다. 한 명령어의 해석이 끝나면 바로 다음 명령어를 해석합니다. 코드의 끝에 도달하면 더 이상 명령어를 해석할 수 없으므로 해석을 끝냅니다. ====한글 글자 해석 단계==== - `형항핫흣흡흑`의 6글자 중 하나를 만나면, 해당 글자를 해석하고 [말줄임표 해석 단계]로 이동합니다. - `혀`를 만나면 이 위치 다음 `엉`이 처음으로 나오는 곳까지 해석하고 [말줄임표 해석 단계]로 이동합니다. - `엉`이 나오지 않는다면 이 위치의 `혀`를 해석할 수 없는 문자로 처리합니다. - `하`를 만나면 이 위치 다음 `앙` 또는 `앗`이 처음으로 나오는 곳까지 해석하고 [말줄임표 해석 단계]로 이동합니다. - `앙`, `앗`이 나오지 않는다면 이 위치의 `하`를 해석할 수 없는 문자로 처리합니다. - 둘 다 나온다면 둘 중 먼저 나오는 곳까지 해석합니다. - `흐`를 만나면 이 위치 다음 `읏읍윽` 세 글자 중 하나가 처음으로 나오는 곳까지 해석하고 [말줄임표 해석 단계]로 이동합니다. - `읏`, `읍`, `윽`이 나오지 않는다면 이 위치의 `흐`를 해석할 수 없는 문자로 처리합니다. - 셋 다 나온다면 셋 중 가장 먼저 나오는 곳까지 해석합니다. ====해석할 때 유의점==== - 해석할 글자들 중 한글 음절 문자가 아닌 문자는 모두 무시합니다. - 해석할 글자들 중 한글 음절 문자만 `글자 수`로 카운트합니다. ====말줄임표 해석 단계==== 1. `i`를 `0`으로 잡습니다. 아래의 과정을 진행하는 중 명령어 해석이 끝나거나 다른 해석 단계로 넘어갈 경우 `i`는 `마침표 개수`가 됩니다. 2. 코드의 끝에 도달하면 명령어 해석을 끝냅니다. 3. 현재 위치에서부터 [한글 글자 해석 단계]에 의해 해석이 가능하다면 명령어 해석을 끝냅니다. 4. 하트-물음표-느낌표에서 쓰이는 하트들과 `?`, `!` 중 하나를 만나면 하트 구역 해석 단계로 갑니다. 5. `.`을 만나면 `i`에 1을 더해 넣고 과정 `2.`로 돌아갑니다. 이 문자는 해석되었다고 봅니다. 6. `…`, `⋯`, `⋮`중 하나를 만나면 `i`에 3을 더해 넣고 과정 `2.`로 돌아갑니다. 이 문자는 해석되었다고 봅니다. ====하트 구역 해석 단계==== 1. 현재 위치에서부터, 코드의 끝 또는 한글 글자 해석 단계에 의해 해석이 가능하기 직전까지의 구간 중 가장 짧은 문자열 구간을 잡습니다. 2. 구간 내에서 하트-물음표-느낌표에서 쓰이는 하트들과 `?`, `!`를 제외한 모든 문자를 제거합니다. 3. 이 구간을 `구역`으로 해석합니다. - 구역의 해석에 관해서는 하트-물음표-느낌표에 언급됩니다. 4. 명령어 해석을 끝냅니다. ==예시== ====Hello, world!==== || 혀어어어어어어어엉........ 핫. 혀엉..... 흑... 하앗... 흐윽... 형. 하앙. 혀엉.... 하앙... 흐윽... 항. 항. 형... 하앙. 흐으윽... 형... 흡... 혀엉.. 하아아앗. 혀엉.. 흡... 흐읍... 형.. 하앗. 하아앙... 형... 하앙... 흐윽... 혀어어엉.. 하앙. 항. 형... 하앙. 혀엉.... 하앙. 흑... 항. 형... 흡 하앗. 혀엉..... 흑. 흣 || ==기타== - 테스트 케이스 모음: [[https://github.com/xnuk/hyeong-testcases]] - 웹에서 돌려보기: [[https://xnuk.github.io/elmhyeong/]]