Skip to content

Latest commit

 

History

History
165 lines (101 loc) · 9.95 KB

spec.md

File metadata and controls

165 lines (101 loc) · 9.95 KB

명세

'평범한 한글' 프로그램은 한글과 그 외 문자로 이루어진 문자열입니다. 여기서 한글은 다음의 범위에 들어가는 유니코드 문자를 말합니다.

  • U+1100-U+11FF (한글 자모)
  • U+302E-U+302F (방점)
  • U+3131-U+318E (호환용 한글 자모)
  • U+A960-U+A97C (한글 자모 확장 A)
  • U+AC00-U+D7AF (한글 음절)
  • U+D7B0-U+D7C6, U+D7CB-U+D7FB (한글 자모 확장 B)
  • U+FFA1-U+FFBE, U+FFC2-U+FFC7, U+FFCA-U+FFCF, U+FFD2-U+FFD7, U+FFDA-U+FFDC (반각 한글 자모)

한글은 다시 초성(호환용 한글 자모와 반각 한글 자모는 자음) 및 그 외 한글로 구분해, 초성이 아닌 한글은 삭제하고 초성은 해당하는 호환용 한글 자모로 변환합니다. 한글 음절은 초성만 추출하여 변환합니다. 편의를 위해 본 명세에서 한글은 호환용 한글 자모 중 자음이 대표합니다. 변환 예시는 다음과 같습니다.

  • 동해물과 백두산이 = ㄷㅎㅁㄱ ㅂㄷㅅㅇ
  • 나랏〮말〯ᄊᆞ미〮 듕귁〮에〮달아〮 = ㄴㄹㅁㅆㅁ ㄷㄱㅇㄷㅇ

한편 한글이 아닌 문자는 달리 구분하지 않고 공백과 같이 생각합니다. 따라서 본 명세에도 한글이 아닌 문자는 공백이 대표합니다.

거센소리와 된소리는 예사소리와 같게 취급하며, 겹자음은 자음 여러 개로 분리하여 생각합니다. 이는 작문 시 단어 선택의 폭을 넓히기 위한 선택입니다. 예시로, 다음 단어쌍들은 동일합니다.

  • ㄱㄲㅅㄹ = ㄱㄱㅅㄹ
  • ㅇㅊㅂㅌ = ㅇㅈㅂㄷ
  • ㄳㅎㄴㄷ = ㄱㅅㅎㄴㄷ

간결한 설명을 위해 예사소리 단자음 10개 ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅎ만 사용하여 설명하겠습니다. 또한, 모든 은 그 앞에 공백이 있는 것과 없는 것을 같게 봅니다. 따라서 편의를 위해 모든 앞에 미리 공백을 추가했다고 가정하겠습니다.

가. 정수 리터럴

을 제외한 8개 자음 ㄱㄴㄷㄹㅁㅂㅅㅈ은 8진수 가변 길이 정수를 부호화하기 위한 목적으로 사용됩니다.

  • 자음 ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅂ, ㅅ, ㅈ은 각각 8진수 숫자 0, 1, 2, 3, 4, 5, 6, 7을 뜻합니다.
  • 가장 낮은 자리수가 가장 먼저 옵니다. 즉, 역순으로 부호화합니다. 예를 들어, 8진수 210은 ㄱㄴㄷ로 적습니다.
  • 자음 개수가 홀수면 양수를, 자음 개수가 짝수면 음수를 뜻합니다. 뒤에 (0)을 덧붙이는 방법으로 원하는 부호를 지정할 수 있습니다.

예시는 다음과 같습니다.

  • (oct +0) = 0
  • (oct +1) = 1
  • ㄴㄱ (oct -01) = -1
  • ㄴㄱㄱ (oct +001) = 1
  • ㄱㄴ (oct -10) = -8
  • ㄱㄴㄱ (oct +010) = +8
  • ㄱㄱㄴ (oct +100) = +64
  • ㄱㄱㄴㄱ (oct -0100) = -64

나. 단어

단어란 공백을 포함하지 않는 가장 긴 연속한 한글열을 말합니다. 예를 들어 프로그램 ㄱ ㄴㄷ ㄱ ㅎㄷ은 단어 네 개 , ㄴㄷ, , ㅎㄷ으로 이루어져 있습니다.

다. 객체와 자료형

객체는 프로그램 실행의 기본 단위입니다. 객체는 ㄱㄴㄷㄹㅁㅂㅅㅈ으로만 단어를 구성한 정수 리터럴이거나, 함수 만들기 혹은 함수 호출로 만들어진 것이며, 함수의 인수로 사용할 수 있습니다. 객체의 자료형은 다음 열두 가지입니다.

  • 정수
  • 실수
  • 복소수
  • 논릿값
  • 문자열
  • 바이트열
  • 목록
  • 사전
  • 함수
  • 드나듦
  • 예외
  • 빈값

라. 함수

만들기

모든 함수는 0개 이상의 객체를 인수로 받아 객체 하나를 돌려줍니다. 함수를 만들 때 인수를 몇 개 받을지는 따로 표기하지 않습니다. 함수를 만드는 표현식은 다음과 같이 함수 몸통이 되는 객체 하나와 단어 으로 이루어지며, 표현식 전체가 함수 객체가 됩니다.

<함수 몸통> ㅎ

예를 들어 인수에 상관 없이 정수 3을 돌려주는 함수는 ㄹ ㅎ으로 만듭니다.

함수 호출

인수 n개를 써서 함수를 호출하는 표현식은 다음과 같이, (n+1)개의 객체에 이어 뒤에 인수 개수 n을 부호화한 정수 리터럴을 붙인 단어 1개로 구성됩니다. 마지막 객체가 호출할 함수이고, 첫 n개의 객체가 이 함수에 인수로 전달됩니다.

<인수0> <인수1> <인수2> ... <인수n-1> <함수> ㅎ<정수 리터럴 n>

인수에 상관 없이 정수 3을 돌려주는 함수에 두 개의 인수 1과 -1을 전달하여 부르는 표현식은 다음과 같습니다.

ㄴ ㄴㄱ ㄹ ㅎ ㅎㄷ

기본 제공 함수

본래 호출할 함수는 함수 객체여야 하지만, 정수 리터럴에 한해 정수 객체를 허용하며, 복소수, 논릿값, 사전, 목록, 문자열 및 바이트열 객체도 허용합니다. 자세한 내용은 기본 제공 함수 문서를 참조해주세요.

재귀 함수

함수를 만들 때 만들려는 함수와 둘러싼 함수를 함수 몸통에서 쓸 수 있습니다. 만들려는 함수를 0번째라고 하고, k번째 함수를 바로 둘러싼 함수를 (k+1)번째라고 합시다. m번째 함수를 쓰려면 다음과 같이 합니다.

<정수 리터럴 m> ㅇ

예를 들어 1번째 함수를 쓰려면 다음과 같이 합니다.

ㄴ ㅇ

m이 음수인 경우 바깥쪽에서 (-m-1)번째 함수를 씁니다. 참고로 이 번호는 함수 객체 안에 저장되어 함수 객체를 어느 문맥에서 부르더라도 일관된 행동을 보장합니다.

마. 인수

인수 가져오기

함수를 만들 때 만들려는 함수와 둘러싼 함수에 전달되는 인수를 사용할 수 있습니다. 앞서와 같이 함수에 번호를 매겨서, m번째 함수의 n번째 인수를 가져오는 표현식은 다음과 같습니다. m이 음수인 경우 바깥쪽에서 (-m-1)번째 함수의 n번째 인수를 가져옵니다. 다만 n이 음수인 경우는 금지됩니다.

<정수 리터럴 n> ㅇ<정수 리터럴 m>

예를 들어 자기가 받은 0번째 인수를 내놓는 함수는 다음과 같습니다.

ㄱ ㅇㄱ ㅎ

또, 자기가 받은 0번째 인수와 1번째 인수를 더해 내놓는 함수는 다음과 같습니다.

ㄱ ㅇㄱ ㄴ ㅇㄱ ㄷ ㅎㄷ ㅎ

한편 함수 λx.λy.(x+y)는 다음과 같이 작성할 수 있습니다.

ㄱ ㅇㄴ ㄱ ㅇㄱ ㄷ ㅎㄷ ㅎ ㅎ

이 함수에 차례로 3과 4를 인수로 넣어 호출하는 표현식은 다음과 같습니다.

ㄹ ㅁ ㄱ ㅇㄴ ㄱ ㅇㄱ ㄷ ㅎㄷ ㅎ ㅎ ㅎㄴ ㅎㄴ

동적으로 가져오기

몇 번째 인수를 가져올지 미리 정해지지 않으면 동적으로 값을 평가해 그 값으로 가져올 수 있습니다. 다음과 같이 정수 객체 하나와 으로 시작하는 단어 하나로 이뤄진 표현식을 생각합시다. 정수 객체의 값이 n이면, 다음 표현식은 m번째 함수의 n번째 인수를 가져옵니다.

<정수 객체 n> ㅇ<정수 리터럴 m>

예를 들어 ㄱ ㅇㄱ ㅇㄱ ㅎ은 다음 의사 코드로 표현할 수 있습니다.

def func(*argv):
  return argv[argv[0]]

ㄱ ㅇㄱ ㄴ ㄷ ㅎㄷ ㅇㄱ ㅎ은 다음 의사 코드로 표현할 수 있습니다.

def func(*argv):
  return argv[argv[0] + 1]

바. 드나듦(IO)

'평범한 한글'의 드나듦 객체는 하스켈의 IO 모나드(Monad)에서 따왔습니다. 이것은 부작용이 있는 연산을 격리하고 실행 순서를 명확하게 하기 위한 목적입니다. 이를 위해 입력받은 값을 바로 활용하는 것은 금지되고, 대신 그 값을 어떻게 활용할지를 미리 계획합니다. 표현식을 전부 평가했을 때 드나듦 객체가 나오면, 인터프리터는 그때부터 드나듦 객체에 적힌 계획에 따라 입출력 연산을 실행하게 됩니다.

먼저, 기본 제공 함수 을 부르면 표준 입력에서 문자열을 받아오는 계획이 담긴 드나듦 객체를 얻습니다. 이것과 함께 나중에 그 값으로 무엇을 할지 계획한 함수로 기본 제공 함수 ㄱㄹ을 불러 새 드나듦 객체를 만듭니다. 이때 마지막 인수인 함수는 드나듦 객체를 내놓아야 합니다.

예를 들어, 표준 입력에서 문자열을 받아 그대로 표준 출력으로 보내는 계획을 담은 드나듦 객체는 다음과 같습니다.

ㄹ ㅎㄱ ㄱ ㅇㄱ ㅈㄹ ㅎㄴ ㅎ ㄱㄹ ㅎㄷ

여기서 ㄹ ㅎㄱ은 앞서 언급했듯 문자열을 입력받는 계획이 담긴 드나듦 객체입니다. 한편 ㅈㄹ ㅎㄴ은 인수를 출력하는 계획이 담긴 드나듦 객체입니다. 따라서 ㄱ ㅇㄱ ㅈㄹ ㅎㄴ ㅎ은 인수를 받아서 그것을 출력할 드나듦 객체를 만드는 함수입니다. 기본 제공 함수 ㄱㄹ은 드나듦 객체와 드나듦 객체를 만들 함수를 하나씩 인수로 받아 새 드나듦 객체를 만듭니다. 인터프리터는 이 드나듦 객체를 만나면 첫번째 인수를 실행해 값을 얻고, 이것으로 두번째 인수를 부릅니다. 그 결과로 인터프리터는 표준 출력을 하는 드나듦 객체를 만나, 출력을 수행하고 끝냅니다.

다른 예로, 입력값을 받아 실수로 변환하는 프로그램은 다음과 같습니다. 여기서 기본 제공 함수 ㄱㅅ은 나중에 실행되면 인수로 받은 값을 그대로 돌려주는 드나듦을 만듭니다.

ㄹ ㅎㄱ ㅅㅅ ㄱㅅ ㄴㄱ ㅎㄷ ㄱㄹ ㅎㄷ

여기서 함수 ㅅㅅ ㄱㅅ ㄴㄱ ㅎㄷ은 문자열을 실수로 변환해 드나듦 객체로 감싸 내놓는 함수입니다. 기본 제공 함수 ㄱㄹ을 불러서 받은 드나듦 객체와 이 함수를 묶어 새 드나듦 객체를 만듭니다. 이후에 이 드나듦 객체가 실행되면 먼저 드나듦 ㄹ ㅎㄱ을 실행해 값을 받은 후 이것을 인수로 위 함수를 불러 최종 드나듦을 받게 됩니다. 이것까지 실행하면 원하는 실수값이 나옵니다.