Lisp 프로그래머는 Lisp가 매우 작은 기본 연산 세트에서 구축 할 수 있는 강력한 언어라고 자랑합니다 . 이라는 방언을 위해 통역사에게 골프를 쳐서 그 아이디어를 실천해 봅시다 tinylisp
.
언어 사양
이 사양에서 결과가 "정의되지 않음"으로 표시되는 모든 조건은 인터프리터에서 충돌, 자동 실패, 임의의 고블 덕킹 생성 또는 예상대로 작동하는 모든 작업을 수행 할 수 있습니다. Python 3의 참조 구현은 여기에서 사용할 수 있습니다 .
통사론
tinylisp에서 토큰은 (
, )
또는 괄호 또는 공간을 제외한 하나 개 이상의 인쇄 가능한 ASCII 문자의 문자열입니다. (즉, 다음 정규식 : [()]|[^() ]+
.) 완전히 숫자 로 구성된 토큰 은 정수 리터럴입니다. (선행 제로 괜찮아요.) 비 숫자를 포함 모든 토큰은 심지어 숫자 보이는 예처럼, 상징 123abc
, 3.14
그리고 -10
. 토큰을 분리하지 않는 한 모든 공백 문자 (최소 ASCII 문자 32 및 10 포함)는 무시됩니다.
tinylisp 프로그램은 일련의 표현식으로 구성됩니다. 각 표현식은 정수, 기호 또는 s- 표현식 (목록)입니다. 목록은 괄호로 묶인 0 개 이상의 식으로 구성됩니다. 항목 사이에 구분 기호가 사용되지 않습니다. 식의 예는 다음과 같습니다.
4
tinylisp!!
()
(c b a)
(q ((1 2)(3 4)))
제대로 구성되지 않은 (특히 일치하지 않는 괄호가있는) 식은 정의되지 않은 동작을 제공합니다. (참조 구현은 열린 parens를 자동으로 닫고 일치하지 않는 close parens에서 구문 분석을 중지합니다.)
자료형
tinylisp의 데이터 유형은 정수, 기호 및 목록입니다. 내장 함수 및 매크로는 출력 형식이 정의되지 않았지만 유형으로 간주 될 수도 있습니다. 목록에는 모든 유형의 값이 얼마든지 포함될 수 있으며 임의로 중첩 될 수 있습니다. 정수는 최소한 -2 ^ 31에서 2 ^ 31-1까지 지원되어야합니다.
빈 목록 ( ()
nil이라고도 함)과 정수 0
는 논리적으로 거짓으로 간주되는 유일한 값입니다. 다른 모든 정수, 비어 있지 않은 목록, 내장 및 모든 기호는 논리적으로 사실입니다.
평가
프로그램의 표현식은 순서대로 평가되며 각각의 결과는 stdout으로 전송됩니다 (나중에 출력 형식화에 대한 자세한 내용).
- 정수 리터럴은 자체적으로 평가됩니다.
- 빈 목록
()
은 그 자체로 평가됩니다. - 하나 이상의 항목 목록은 첫 번째 항목을 평가하여 함수 또는 매크로로 취급하여 나머지 항목을 인수로 호출합니다. 항목이 함수 / 매크로가 아닌 경우 동작이 정의되지 않습니다.
- 기호는 이름으로 평가되어 현재 함수에서 해당 이름에 바인딩 된 값을 제공합니다. 이름이 현재 함수에 정의되어 있지 않으면 전역 범위에서 해당 값으로 바인드됩니다. 이름이 현재 또는 전역 범위에서 정의되지 않은 경우 결과가 정의되지 않습니다 (참조 구현은 오류 메시지를 제공하고 nil을 리턴 함).
내장 함수 및 매크로
tinylisp에는 7 개의 내장 함수가 있습니다. 함수는 일부 인수를 적용하고 결과를 반환하기 전에 각 인수를 평가합니다.
c
-단점 [truct list]. 두 개의 인수 (값과 목록)를 사용하고 목록 앞에 값을 추가하여 얻은 새 목록을 반환합니다.h
-머리 ( 자동차 , Lisp 용어로). 목록을 가져 와서 첫 번째 항목을 반환하거나 nil이 지정된 경우 nil을 반환합니다.t
-꼬리 ( Lisp 용어에서 cdr ). 목록을 가져 와서 첫 번째 항목을 제외한 모든 항목을 포함하는 새 목록을 반환하거나 nil이 지정된 경우 nils
-빼기. 두 개의 정수를 취하고 첫 번째 빼기 두 번째 정수를 반환합니다.l
-이하 두 개의 정수를 취합니다. 첫 번째가 두 번째보다 작 으면 1을, 그렇지 않으면 0을 반환합니다.e
-동일 동일한 유형의 두 값 (정수, 두 목록 또는 두 기호 모두)을 취합니다. 두 요소가 같거나 모든 요소에서 동일하면 1을, 그렇지 않으면 0을 반환합니다. 평등에 대한 내장 테스트는 정의되지 않았습니다 (참조 구현은 예상대로 작동 함).v
-평가. 표현식을 나타내는 하나의 목록, 정수 또는 기호를 가져 와서 평가합니다. 예를 들어하는(v (q (c a b)))
것은하는 것과 같습니다(c a b)
.(v 1)
제공합니다1
.
여기에 "값"은 달리 지정되지 않는 한 모든 목록, 정수, 기호 또는 내장을 포함합니다. 함수가 특정 유형을 취하는 것으로 나열되면 잘못된 유형의 인수를 전달하는 것처럼 다른 유형을 전달하는 것은 정의되지 않은 동작입니다 (참조 구현은 일반적으로 충돌).
tinylisp에는 3 개의 내장 매크로가 있습니다. 매크로는 함수와 달리 연산을 적용하기 전에 인수를 평가하지 않습니다.
q
- 인용문. 하나의 표현식을 사용하여 평가되지 않은 상태로 리턴합니다. 예를 들어, 평가(1 2 3)
는1
함수 또는 매크로 로 호출하려고 시도 하지만(q (1 2 3))
list를 반환 하므로 오류가 발생 합니다(1 2 3)
. 평가a
하면 name에 바인딩 된 값이 제공a
되지만(q a)
이름 자체는 제공됩니다.i
- 만약. 조건, iftrue 표현식 및 iffalse 표현식의 세 가지 표현식을 사용합니다. 먼저 조건을 평가합니다. 결과가 거짓 (0
또는 nil)이면 iffalse 표현식을 평가하고 리턴합니다. 그렇지 않으면 iftrue 표현식을 평가하고 리턴합니다. 리턴되지 않은 표현식은 평가되지 않습니다.d
-방어력 기호와 표현을받습니다. 식을 평가하여 전역 범위에서 이름으로 취급되는 지정된 기호에 바인딩 한 다음 기호를 반환합니다. 이름을 재정의하려고하면 실패합니다 (자동으로 메시지 또는 충돌로 인해 참조 구현에 오류 메시지가 표시됨). 참고 : 이름을 전달하기 전에 이름d
을 인용 할 필요는 없지만 평가하지 않으려는 목록 또는 기호 인 경우 표현식을 인용해야합니다 (예 :)(d x (q (1 2 3)))
.
매크로에 잘못된 수의 인수를 전달하는 것은 정의되지 않은 동작입니다 (참조 구현 충돌). 첫 번째 인수로 기호가 아닌 것을 전달하는 d
것은 정의되지 않은 동작입니다 (참조 구현에는 오류가 발생하지 않지만 이후에 값을 참조 할 수는 없습니다).
사용자 정의 함수 및 매크로
이 10 가지 내장 기능부터 새로운 기능과 매크로를 구성하여 언어를 확장 할 수 있습니다. 여기에는 전용 데이터 유형이 없습니다. 그들은 단순히 특정 구조를 가진 목록입니다.
- 기능은 두 항목의 목록입니다. 첫 번째는 하나 이상의 매개 변수 이름 목록이거나 함수에 전달 된 모든 인수 목록을받는 단일 이름입니다 (따라서 가변 배열 함수 허용). 두 번째는 함수 본문 인 표현식입니다.
- 매크로는 매개 변수 이름 앞에 nil을 포함한다는 점을 제외하면 함수와 동일하므로 3 개의 항목 목록이됩니다. nil로 시작하지 않는 3 개의 항목 목록을 호출하는 것은 정의되지 않은 동작입니다. 참조 구현은 첫 번째 인수를 무시하고 매크로로도 취급합니다.
예를 들어 다음 표현식은 두 개의 정수를 더하는 함수입니다.
(q List must be quoted to prevent evaluation
(
(x y) Parameter names
(s x (s 0 y)) Expression (in infix, x - (0 - y))
)
)
그리고 많은 수의 인수를 취하고 첫 번째를 평가하고 반환하는 매크로 :
(q
(
()
args
(v (h args))
)
)
함수와 매크로는 직접 호출하고을 사용하여 이름에 바인딩 한 d
후 다른 함수 나 매크로에 전달할 수 있습니다.
함수 본문은 정의시 실행되지 않으므로 재귀 함수는 쉽게 정의 할 수 있습니다.
(d len
(q (
(list)
(i list If list is nonempty
(s 1 (s 0 (len (t list)))) 1 - (0 - len(tail(list)))
0 else 0
)
))
)
그러나 위의 방법은 길이 함수를 사용하지 않기 때문에 길이 함수를 정의하는 좋은 방법이 아닙니다.
테일 콜 재귀
테일 콜 재귀 는 Lisp에서 중요한 개념입니다. 특정 종류의 재귀를 루프로 구현하여 호출 스택을 작게 유지합니다. tinylisp 인터프리터 는 적절한 꼬리 호출 재귀를 구현 해야합니다 !
- 사용자 정의 함수 또는 매크로의 리턴 표현식이 다른 사용자 정의 함수 또는 매크로에 대한 호출 인 경우 인터프리터는 재귀를 사용하여 해당 호출을 평가해서는 안됩니다. 대신, 현재 함수 및 인수를 새로운 함수 및 인수로 바꾸고 호출 체인이 해결 될 때까지 루프해야합니다.
- 사용자 정의 함수 또는 매크로의 리턴 표현식이에 대한 호출 인
i
경우 선택한 분기를 즉시 평가하지 마십시오. 대신 다른 사용자 정의 함수 또는 매크로에 대한 호출인지 확인하십시오. 그렇다면 위와 같이 함수와 인수를 교체하십시오. 이는 임의로 중첩 된의 발생에 적용됩니다i
.
테일 재귀는 직접 재귀 (함수 호출)와 간접 재귀 (함수를 a
호출 b
하는 [etc]를 호출 하는 함수 호출)에 모두 작동해야합니다 a
.
꼬리 재귀 길이 함수 (도우미 기능 포함 len*
) :
(d len*
(q (
(list accum)
(i list
(len*
(t list)
(s 1 (s 0 accum))
)
accum
)
))
)
(d len
(q (
(list)
(len* list 0)
))
)
이 구현은 최대 정수 크기로만 제한되는 임의로 큰 목록에 작동합니다.
범위
함수 매개 변수는 지역 변수입니다 (실제로 상수는 수정할 수 없기 때문에 상수입니다). 해당 함수의 해당 호출 본문이 실행되는 동안 범위 내에 있으며 더 깊은 호출 중에 그리고 함수가 리턴 된 후에 범위를 벗어납니다. 전역 적으로 정의 된 이름을 "그림자"로 만들면 전역 이름을 일시적으로 사용할 수 없게됩니다. 예를 들어 다음 코드는 41이 아니라 5를 반환합니다.
(d x 42)
(d f
(q (
(x)
(s x 1)
))
)
(f 6)
그러나 다음 코드는 x
호출 레벨 1에서 호출 레벨 2에서 액세스 할 수 없으므로 41을 리턴합니다 .
(d x 42)
(d f
(q (
(x)
(g 15)
))
)
(d g
(q (
(y)
(s x 1)
))
)
(f 6)
주어진 시간에 범위 내에서 유일한 이름은 1) 현재 실행중인 함수의 로컬 이름 (있는 경우) 및 2) 전역 이름입니다.
제출 요건
입력과 출력
통역사는 stdin 또는 stdin 또는 명령 줄 인수를 통해 지정된 파일에서 프로그램을 읽을 수 있습니다. 각 표현식을 평가 한 후에는 해당 표현식의 결과를 후행 줄 바꿈으로 stdout에 출력해야합니다.
- 정수는 구현 언어의 가장 자연스러운 표현으로 출력되어야합니다. 빼기 부호를 사용하여 음수를 출력 할 수 있습니다.
- 기호는 따옴표 나 이스케이프 없이 문자열로 출력해야합니다 .
- 모든 항목은 공백으로 구분하고 괄호로 묶어서 목록을 출력해야합니다. 괄호 안의 공백은 선택 사항
(1 2 3)
이며( 1 2 3 )
둘 다 허용되는 형식입니다. - 내장 함수 및 매크로 출력은 정의되지 않은 동작입니다. (참조 해석은이를으로 표시합니다
<built-in function>
.)
다른
참조 인터프리터는 REPL 환경과 다른 파일에서 작은 모듈을로드하는 기능을 포함합니다. 이들은 편의상 제공되며이 도전에는 필요하지 않습니다.
테스트 사례
테스트 케이스는 여러 그룹으로 구분되므로 더 복잡한 것을 테스트하기 전에 더 간단한 것을 테스트 할 수 있습니다. 그러나 하나의 파일로 모두 덤프하면 잘 작동합니다. 제목과 예상 출력을 실행하기 전에 제거하는 것을 잊지 마십시오.
tail-call 재귀를 올바르게 구현 한 경우 스택 오버플로를 발생시키지 않고 최종 (다중 부분) 테스트 사례가 반환됩니다. 참조 구현은 랩톱에서 약 6 초 안에이를 계산합니다.
-1
은 없지만을 수행하여 값 -1을 생성 할 수 있습니다 (s 0 1)
.
F
함수에서 사용할 수 없지만 내부에 정의 된 중첩 함수 (어휘 범위와 같이) 인 경우 함수에서도 사용할 수 없습니다. 테스트 사례 5를 참조하십시오. "오해의 소지가 있습니다. G
F
G
H
H
F