작은 해석기, 작은 통역


33

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이 지정된 경우 nil
  • s-빼기. 두 개의 정수를 취하고 첫 번째 빼기 두 번째 정수를 반환합니다.
  • 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 초 안에이를 계산합니다.


"완전히 숫자로 구성된 토큰은 정수 리터럴입니다. (제로 0은 괜찮습니다.) 숫자가 아닌 토큰은 123abc, 3.14 및 -10과 같은 숫자 모양의 예 일지라도 상징입니다." "정수는 적어도 -2 ^ 31에서 2 ^ 31-1까지 지원되어야합니다."
msh210

3
@ msh210별로, 전자는 얘기 때문에 토큰 에 대해 이야기하고 후자 반면 . 직접 입력하는 방법 -1은 없지만을 수행하여 값 -1을 생성 할 수 있습니다 (s 0 1).
DLosc

1
@coredump 관련 Wikipedia 기사를 읽은 후 구현이 실제로 동적에 가깝지만 범위 중첩이 없다고 결론을 내 렸습니다. 함수의 변수는 동적 범위 지정과 같이 호출 할 경우 F함수에서 사용할 수 없지만 내부에 정의 된 중첩 함수 (어휘 범위와 같이) 인 경우 함수에서도 사용할 수 없습니다. 테스트 사례 5를 참조하십시오. "오해의 소지가 있습니다. GFGHHF
DLosc

1
다시 말하면 범위 중첩이 없기 때문에 구현에서 동적 또는 어휘 범위 지정 전략을 사용하고 동일한 결과를 얻을 수 있습니다. 주어진 시간에 범위 내에서 유일한 이름은 1) 현재 실행중인 함수의 로컬 이름 (있는 경우) 및 2) 전역 이름입니다. 폐쇄 는 지원되지 않습니다. (참조 구현은 호출 스택에 대응하는 이름 바인딩 스택을 유지합니다. 동적 스타일 접근 방식으로 구현하기가 가장 쉽다고 생각합니다.)
DLosc

1
필수 xkcd .
mınxomaτ

답변:


11

파이썬 2, 685 675 660 657 646 642 640 바이트

import sys,re
E=[]
G=zip("chtsle",[eval("lambda x,y=0:"+f)for f
in"[x]+y (x+[E])[0] x[1:] x-y +(x<y) +(x==y)".split()])
def V(e,L=E):
 while 1:
    try:return e and int("0%s"%e)
    except:A=e[1:]
    if""<e:return dict(G+L).get(e,e)
    f=V(e[0],L)
    if""<f:
     if f in"iv":t=V(A[0],L);e=(e[~bool(t)],t)[f>"u"];continue
     if"e">f:G[:]+=(A[0],V(A[1],L)),
     return A[0]
    if[]>f or f[0]:A=[V(a,L)for a in A]
    if[]>f:return f(*A)
    P,e=f[-2:];L=([(P,A)],zip(P,A))[P<""]
F=lambda x:V<x<""and"(%s)"%" ".join(map(F,x))or"%s"%x
for t in re.sub("([()])"," \\1 ",sys.stdin.read()).split():
 if")"==t:t=E.pop()
 if"("==t:E+=[],
 elif E:E[-1]+=t,
 else:print F(V(t))

STDIN에서 입력을 읽고 STDOUT에 출력을 씁니다.

반드시 필요한 것은 아니지만 인터프리터는 nullary 함수와 매크로를 지원하고을 통해 실행되는 테일 호출을 최적화합니다 v.

설명

파싱

입력을 분석하기 위해 먼저 각각의 선두로부터 서라운드 ()공백을 단어로 결과 스트링을 분할; 이것은 우리에게 토큰 목록을 제공합니다. E처음에는 비어 있는 표현식 스택을 유지합니다 . 다음 순서대로 토큰을 스캔합니다.

  • 을 만나면 (표현식 스택의 맨 위에 빈 목록을 넣습니다.
  • 을 만나면 )표현식 스택의 맨 위에 값을 넣고 이전에 스택에서 그 아래에 있던 목록에 추가합니다.
  • 그렇지 않으면 현재 토큰을 문자열로 표현 스택의 맨 위에있는 목록에 추가합니다 (이 단계에서는 정수를 문자열로 유지하고 평가 중에 구문 분석합니다).

일반 토큰을 processsing 때 경우, 또는 인해 스택에서 식을 진열 한 후 ), 식 스택이 비어, 우리는 최고 수준의 표현에있어, 우리는 우리가 달리 사용, 추가 한 것입니다 가치를 평가 V()하고, 를 사용하여 적절하게 형식화 된 결과를 인쇄하십시오 F().

평가

우리는 전역 범위 G를 키 / 값 쌍의 목록으로 유지합니다 . 처음에는 v람다로 구현되는 내장 함수 (매크로가 아닌 매크로가 아닌) 만 포함합니다 .

평가는 내부 발생 V(), 평가 식을 걸립니다, e그리고 지역 범위 L(최고 수준의 표현을 평가할 때, 로컬 범위가 비어 있습니다.)의 내장, 키 / 값 쌍의 목록을 너무입니다, V()라이브 무한 루프 내에서, 이것은 나중에 설명 하듯이 테일 콜 최적화 (TCO)를 수행하는 방법입니다.

e유형에 따라 처리 합니다.

  • 빈 목록이거나 문자열을 int로 변환 할 수있는 경우 즉시 반환합니다 (int로 변환 한 후). 그렇지 않으면,

  • 문자열이면 전역 범위와 로컬 범위의 연결로 구성된 사전에서 문자열을 찾습니다. 관련된 값을 찾으면 반환합니다. 그렇지 않으면 e내장 매크로 (예 q: i, d또는 v) 의 이름이어야하며 변경되지 않은 상태로 반환합니다. 그렇지 않으면 e문자열이 아닌 경우

  • e(비 공백) 목록, 즉 함수 호출입니다. 우리는 V()재귀 적 으로 호출함으로써 (현재 로컬 범위를 사용하여) 목록의 첫 번째 요소, 즉 함수 표현식을 평가합니다 . 우리는 결과를 호출합니다 f. 목록의 나머지 부분은 A인수 목록입니다. f문자열 일 수 있습니다.이 경우에는 내장 매크로 (또는 함수 v), 람다 (내장 함수) 또는 목록 (이 경우 사용자 정의 함수 또는 매크로)입니다.

    f문자열 인 경우 , 즉 내장 매크로 인 경우에는 해당 위치에서 처리합니다. 매크로 i이거나 v이면 첫 번째 피연산자를 평가하고의 경우에 따라 두 번째 또는 세 번째 피연산자를 선택하거나 i첫 번째 피연산자의 결과를 사용합니다 v. 선택된 표현식을 재귀 적으로 평가하는 대신 TCO를 물리 칠 수 있습니다. 단순히 e해당 표현식으로 바꾸고 루프의 시작으로 넘어갑니다. f매크로 d인 경우 첫 번째 요소가 첫 번째 피연산자이고 두 번째 요소가 두 번째 피연산자 평가 결과 인 쌍을 전역 범위에 추가 G하고 첫 번째 피연산자를 반환합니다. 그렇지 않으면, f매크로 q입니다.이 경우 피연산자를 직접 반환합니다.

    경우 Othrtwise, f람다, 또는 그 첫 번째 요소가 아닌 목록입니다 (), 그것은 우리가 그 예 인수의 요소를 평가하는 경우에 비 null의 기능이 아닌 매크로,의 A, 및 교체 A결과로.

    f람다 인 경우 호출하여 압축을 푼 인수를에 전달 A하고 결과를 반환합니다.

    그렇지 않으면 f목록, 즉 사용자 정의 함수 또는 매크로입니다. 매개 변수 목록은 두 번째부터 마지막 ​​요소이며 본문은 마지막 요소입니다. 매크로의 경우와 마찬가지로 i및 것은 v, 총 소유 비용 (TCO)을 수행하기 위해, 우리는 반복적으로 몸을 평가하지 않고, 대신 e몸으로하고 다음 반복을 계속합니다. 달리 i하고 v있지만, 우리는 또한 로컬 범위를 대체, L함수의 새로운 로컬 범위와. 매개 변수리스트, P가 실제로리스트 인 경우 P, 인수리스트로 매개 변수리스트를 압축하여 새로운 로컬 범위를 구성합니다 A. 그렇지 않으면, 우리는 가변 함수를 다루고 있는데,이 경우 새로운 로컬 스코프에는 한 쌍의 요소 만 (P, A)있습니다.

교체

그것을 가지고 놀고 싶다면 여기 인터프리터의 REPL 버전이 있습니다. 심볼 재정의와 명령 줄 인수 또는 (import <filename>)매크로를 통한 파일 가져 오기를 지원합니다 . 인터프리터를 종료하려면 입력을 종료하십시오 (보통 Ctrl + D 또는 Ctrl + Z).

다음은 병합 정렬을 구현하는 예제 세션입니다.


zlib를 사용하여 더 짧은 것을 얻을 수 있습니다 :) 바이트로 변환 된 코드를 압축하고 다음과 같이 import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
바꿉니다

당신은 할당하여 2 바이트를 절약 할 수 A[0]단지 블록을 제외한 후 약간의 한 문자 변수에
한스 Karppila에게

@HannesKarppila 그렇습니다. 그러나 이것은 널 함수를 깨뜨릴 것입니다 ( A이 경우에는 비어 있기 때문에 ).
Ell

4

C (GNU), 1095 바이트

대부분의 동작은 거대한 v기능 에서 발생 합니다. 대신 명시 적으로 꼬리 재귀를 구현하는, v에서 통화의 많은 있도록 구성되어 있습니다 vvGCC의 꼬리 재귀 최적화에 의해 처리됩니다. 가비지 콜렉션이 없습니다.

이것은 GCC 확장을 많이 사용하므로 gcc로만 컴파일 할 수 있습니다 (명령 사용 gcc -w -Os tl.c). 또한 scanf일반적으로 사용하는 Windows에서는 사용할 수 없었던 일부 확장을 사용합니다. 표준 scanf으로 파서를 작성하는 전망 은 너무 무서웠 ​​기 때문에 대신 Linux VM을 사용하여 프로그램을 테스트했습니다. scanf문자 클래스 없이 파싱하면 아마도 100 바이트 이상이 추가되었을 것입니다.

#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})
#define P printf
#define F(I,B)({for(I;x->c;x=x->l)B;})
#define Z return
typedef struct o{struct o*n,*l,*c;int i,t;}o;E(o a,o b){Z
a.n?!strcmp(a.n,b.n):a.c?b.c&&E(*a.c,*b.c)&E(*a.l,*b.l):!b.c&a.i==b.i;}p(o*x){x->t?P("%d ",x->i):x->n?P("%s ",x->n):F(P("("),p(x->c);P(")"));}o*x,G,N;*C(o*h,o*t){Z
O(c:h,l:t);}o*v(o*x,o*e){o*W(o*l,o*e){Z
l->c?C(v(l->c,e),W(l->l,e)):&N;}o*y,*a,*f;int t;Z
x->c?y=v(x->c,e),x=x->l,t=y->i,t?9/t?a=v(x->c,e),t>7?(t>8?a->c:a->l)?:a:t>6?v(a,e):t<6?x=v(x->l->c,e),t>4?C(a,x):O(t:1,i:t>3?E(*a,*x):t>2?a->i<x->i:a->i-x->i):v((a-&N&&!a->t|a->i?x:x->l)->l->c,e):(t&1&&d(x->c->n,v(x->l->c,e)),x->c):(y->l->l->l?y=y->l:(x=W(x,e)),a=y->c,v(y->l->c,a->n?O(n:a->n,c:x,l:&G):F(f=&G,(f=O(n:a->c->n,c:x->c,l:f),a=a->l);f))):x->n?e->n?strcmp(x->n,e->n)?v(x,e->l):e->c:e:x;}d(o*n,o*x){*v(O(n:""),&G)=(o){n:n,c:x,l:O()};}*R(h){char*z,*q;Z
scanf(" %m[^ \n()]",&q)>0?h=strtoul(q,&z,10),C(*z?O(n:q):O(t:1,i:h),R()):~getchar()&1?q=R(),C(q,R()):&N;}main(i){for(;++i<12;)d(strndup("slecivthqd"+i-2,1),O(i:i));F(x=R(),p(v(x->c,&G)));}

반 ungolfed

typedef struct o o;
struct o {
    char* n;
    o* l, //next in this list
     * c; 
    int i,
        t;
} ;



#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})

E(o a, o b) { //tests equality 
    return
        a.n ? !strcmp(a.n,b.n) :
        a.t ? a.i==b.i :
        a.c ? b.c && E(*a.c,*b.c)&E(*a.l,*b.l) :
        !b.c
    ;
}

#define P printf


p(o*x){
    x->t?P("%d ",x->i):x->n?P("%s ",x->n):({for(P("(");x->c;x=x->l)p(x->c);P(")");});
}


o*_,G,N; //N = nil



o*C(o*h,o*t){return O(c:h,l:t);}


/*
        2 3 4 5 6 7 8 9 10 11
        s l e c i v t h d  q
    */


o* v(o* x, o* e) { //takes list, int, or name
    o*W(o* l, o* e) { //eval each item in list
        return l->c ? C(v(l->c ,e), W(l->l, e)) : &N;
    }

    o*y,*a,*f;int t;
    return x->c ? //nonempty list = function/macro call
        y = v(x->c,e), //evals to function/macro
        x = x->l,   //list position of first arg (if it exists)
        (t=y->t)?   //builtin no., if any
             t>9 ?
              t&1 ? x->c // 11 = q
                : //10 = d
                (d(x->c,v(x->l->c,e)),x->c)
           : (a = v(x->c,e), //eval'd first arg
             t)>7 ? // t/h
                (t > 8 ? a->c : a->l) ?: a
           : t>6 ? //v
                v(a,e)
           : (x = x->l, //position of 2nd arg in list
             t)>5 ? //i
                v( (a->n||a->l||a->i|a->t>1 ? x : x->l)->c, e)
           : (x = v(x->c,e), //evaluated 2nd arg
             t)>4 ? // c
                C(a,x)
           : O(t:1,i:
                t>3 ? E(*a,*x) :  //e
                t>2 ? a->i<x->i : //l
                      a->i-x->i   //s
              )
        :
        (
            y->l->l->l ? //whether this is macro
                y = y->l :
                (x = W(x,e)),  //eval args
            a = y->c,  //a = arg list
            //a = a->n ? x=C(x, &N), C(a, &N) : a, //transform variadic style to normal
            v(y->l->c,
               a->n ? //variadic
                O(n:a->n,c:x,l:&G)
              : ({
                   for(f=&G; a->c; a=a->l,x=x->l)
                      f=O(n:a->c->n, c: x->c, l:f);
                   f;
                })
            )
        )
    :
    x->n ? // name
        e->n ?
            strcmp(x->n,e->n) ?
                v(x,e->l)
            : e->c
        : e
     : x; //int or nil
}

d(o*n,o*x){
    * v(O(n:""),&G) =
        (o){n:n->n,c:x,l:O()};
}


;
o*R(){
    char*z,*q;int h;
return scanf(" %m[^ \n()]",&q)>0?
    h=strtoul(q,&z,10),
    C(*z ? O(n:q) : O(t:1,i:h), R())
: getchar()&1?&N:(q=R(),C(q,R()));
}
main(i) {

    for(;++i<12;) d(O(n:strndup("slecivthdq"+i-2,1)),O(t:i));

    o *q;
    for(q=R(); q->c; q=q->l) p(v(q->c,&G));

}

컴파일 된 실행 파일의 사용법은 무엇입니까? REPL입니까? 파일 이름을 입력으로 사용합니까?
ckjbgames 2012

@ckjbgames stdin에서 프로그램을 읽습니다.
feersum

괜찮아. 나는 당신이 당신의 대답을 편집하고 메모해야한다고 생각합니다.
ckjbgames

1

실론, 2422 바이트

(이것은 아직 가장 긴 골프 프로그램이라고 생각합니다.)

import ceylon.language{sh=shared,va=variable,fo=formal,O=Object}import ceylon.language.meta.model{F=Function}interface X{sh fo V v(S t);sh fo G g;}class G(va Map<S,V>m)satisfies X{v(S t)=>m[t]else nV;g=>this;sh S d(S s,V v){assert(!s in m);m=map{s->v,*m};return s;}}V nV=>nothing;class LC(G c,Map<S,V>m)satisfies X{g=>c;v(S t)=>m[t]else g.v(t);}alias C=>V|Co;interface Co{sh fo C st();}interface V{sh fo C l(X c,V[]a);sh default Boolean b=>0<1;sh fo C vO(X c);sh default V vF(X c){va C v=vO(c);while(is Co n=v){v=n.st();}assert(is V r=v);return r;}}class L(sh V*i)satisfies V{vO(X c)=>if(nonempty i)then i[0].vF(c).l(c,i.rest)else this;equals(O o)=>if(is L o)then i==o.i else 1<0;b=>!i.empty;string=>"(``" ".join(i)``)";hash=>i.hash;sh actual C l(X c,V[]p){value[h,ns,x]=i.size<3then[f,i[0],i[1]]else[m,i[1],i[2]];value n=if(is L ns)then[*ns.i.narrow<S>()]else ns;assert(is S|S[]n,is V x);V[]a=h(c,p);LC lC=if(is S n)then LC(c.g,map{n->L(*a)})else LC(c.g,map(zipEntries(n,a)));return object satisfies Co{st()=>x.vO(lC);};}}class S(String n)satisfies V{vO(X c)=>c.v(this);l(X c,V[]a)=>nV;equals(O o)=>if(is S o)then n==o.n else 1<0;hash=>n.hash;string=>n;}class I(sh Integer i)satisfies V{vO(X c)=>this;l(X c,V[]a)=>nV;equals(O o)=>if(is I o)then i==o.i else 1<0;hash=>i;b=>!i.zero;string=>i.string;}V[]f(X c,V[]a)=>[for(v in a)v.vF(c)];V[]m(X c,V[]a)=>a;L c(X c,V h,L t)=>L(h,*t.i);V h(X c,L l)=>l.i[0]else L();V t(X c,L l)=>L(*l.i.rest);I s(X c,I f,I s)=>I(f.i-s.i);I l(X c,I f,I s)=>I(f.i<s.i then 1else 0);I e(X c,V v1,V v2)=>I(v1==v2then 1else 0);C v(X c,V v)=>v.vO(c);V q(X c,V a)=>a;C i(X c,V d,V t,V f)=>d.vF(c).b then t.vO(c)else f.vO(c);S d(X c,S s,V x)=>c.g.d(s,x.vF(c));class B<A>(F<C,A>nat,V[](X,V[])h=f)satisfies V given A satisfies[X,V+]{vO(X c)=>nV;string=>nat.declaration.name;l(X c,V[]a)=>nat.apply(c,*h(c,a));}{<S->V>*}b=>{S("c")->B(`c`),S("h")->B(`h`),S("t")->B(`t`),S("s")->B(`s`),S("l")->B(`l`),S("e")->B(`e`),S("v")->B(`v`),S("q")->B(`q`,m),S("i")->B(`i`,m),S("d")->B(`d`,m)};[V*]p(String inp){value ts=inp.split(" \n()".contains,1<0,1<0);va[[V*]*]s=[];va[V*]l=[];for(t in ts){if(t in" \n"){}else if(t=="("){s=[l,*s];l=[];}else if(t==")"){l=[L(*l.reversed),*(s[0]else[])];s=s.rest;}else if(exists i=parseInteger(t),i>=0){l=[I(i),*l];}else{l=[S(t),*l];}}return l.reversed;}sh void run(){va value u="";while(exists l=process.readLine()){u=u+l+"\n";}V[]e=p(u);X c=G(map(b));for(v in e){print(v.vF(c));}}

일부 장소에서 두 글자로 된 식별자를 사용하여 몇 바이트 더 골프를 칠 수 있었지만 그에 대한 의미있는 단일 문자가 부족했습니다. 이 방법조차도 실론처럼 보이지는 않지만 ...

이것은 객체 지향 구현입니다.

우리는 V클래스를 구현 하는 값 인터페이스 L(목록-Ceylon 순차를 감싸는 래퍼 V), S(기호-문자열을 감싸는 래퍼), I(정수 – Ceylon 정수를 감싸는 래퍼) 및 B(내장 함수 또는 매크로, 주위를 감싸는 래퍼) 실론 기능).

equals메서드 (및 hash기호에만 필요한 string특성)와 출력 의 표준 특성을 구현하여 표준 Ceylon 평등 표기법을 사용합니다 .

우리는 부울 속성이 b(에서 재정의 기본적으로 사실 IL빈 목록에 대한 반환 false로), 그리고 두 가지 방법 l(즉 함수로이 개체를 사용, 전화) 및 vO(한 단계 평가). 둘 다 값 또는 연속 객체를 반환하여 하나 이상의 단계를 vF평가하고 결과가 더 이상 계속되지 않을 때까지 (완전히 평가) 반복합니다.

컨텍스트 인터페이스를 통해 변수에 액세스 할 수 있습니다. G전역 컨텍스트 ( d내장 을 사용하여 변수를 추가 할 수 있음 )와 LC사용자 컨텍스트의 표현을 평가할 때 활성화되는 로컬 컨텍스트 (전역 컨텍스트로 폴백)에 대한 두 가지 구현 이 있습니다 .

기호 평가는 컨텍스트에 액세스하고, 첫 번째 요소를 먼저 평가 한 다음 호출 메소드를 호출하여 평가합니다 (비어 있지 않은 경우). 호출은리스트와 내장에 의해서만 구현됩니다. 먼저 인수 (매크로가 아니라 함수 인 경우)를 평가 한 다음 실제 흥미로운 작업을 수행합니다. 내장 된 하드 코딩 된 것의 경우에는리스트에 대해 새로운 로컬 컨텍스트를 작성하고 그것으로 계속.

빌트인의 경우 Shift Interpreter 에서 사용한 것과 비슷한 트릭을 사용하여 필요한 인수 유형으로 정의 할 수 있지만 리플렉션을 사용하여 일반 시퀀스로 호출합니다 (호출시 유형 확인). 이렇게하면 함수 / 매크로 내부에서 유형 변환 / 어설 션 번거 로움을 피할 수 있지만 메타 모델 Function객체를 얻을 수 있도록 최상위 함수가 필요 합니다.

p(파싱) 함수는 토큰에 대해 반복 한 후, 공백 및 개행의 괄호 스트링을 분할하고, 스택 및 실행리스트를 이용하여 목록을 만든다.

그런 다음 해석기 run는 진입 점 인 메소드에서이 표현식 목록 (값일뿐)을 가져 와서 각각을 평가 한 후 결과를 인쇄합니다.


아래는 주석이 달린 버전이며 포맷터를 통해 실행됩니다.

내가에서 발견된다 (목록 평가에 대한 몇 가지 오해 여전히와) 골프를 시작하기 전에 이전 버전은 내 Github의 저장소 , 나는 (그렇게보고 있는지 확인 곧이이 1 점을 추가하는 듯합니다 첫 번째 버전 은 원본을 원하는 경우).

//  Tiny Lisp, tiny interpreter
//
// An interpreter for a tiny subset of Lisp, from which most of the
// rest of the language can be bootstrapped.
//
// Question:   https://codegolf.stackexchange.com/q/62886/2338
// My answer:  https://codegolf.stackexchange.com/a/63352/2338
//
import ceylon.language {
    sh=shared,
    va=variable,
    fo=formal,
    O=Object
}
import ceylon.language.meta.model {
    F=Function
}

// context

interface X {
    sh fo V v(S t);
    sh fo G g;
}
// global (mutable) context, with the buildins 
class G(va Map<S,V> m) satisfies X {
    // get entry throws error on undefined variables. 
    v(S t) => m[t] else nV;
    g => this;
    sh S d(S s, V v) {
        // error when already defined
        assert (!s in m);
        // building a new map is cheaper (code-golf wise) than having a mutable one.
        m = map { s->v, *m };
        return s;
    }
}

// This is simply a shorter way of writing "this is not an allowed operation".
// It will throw an exception when trying to access it.
// nV stands for "no value".
V nV => nothing;

// local context
class LC(G c, Map<S,V> m) satisfies X {
    g => c;
    v(S t) => m[t] else g.v(t);
    // sh actual String string => "[local: ``m``, global: ``g``]";
}

// continuation or value
alias C => V|Co;

// continuation
interface Co {
    sh fo C st();
}

// value
interface V {
    // use this as a function and call with arguments.
    // will not work for all types of stuff.
    sh fo C l(X c, V[] a);
    // check the truthiness. Defaults to true, as
    // only lists and integers can be falsy.
    sh default Boolean b => 0 < 1;
    // evaluate once (return either a value or a continuation).
    // will not work for all kinds of expression.
    sh fo C vO(X c);
    /// evaluate fully
    sh default V vF(X c) {
        va C v = vO(c);
        while (is Co n = v) {
            v = n.st();
        }
        assert (is V r = v);
        return r;
    }
}
class L(sh V* i) satisfies V {

    vO(X c) => if (nonempty i) then i[0].vF(c).l(c, i.rest) else this;
    equals(O o) => if (is L o) then i == o.i else 1 < 0;
    b => !i.empty;
    string => "(``" ".join(i)``)";
    hash => i.hash;

    sh actual C l(X c, V[] p) {
        value [h, ns, x] =
                i.size < 3
                then [f, i[0], i[1]]
                else [m, i[1], i[2]];
        // parameter names – either a single symbol, or a list of symbols.
        // If it is a list, we filter it to throw out any non-symbols.
        // Should throw an error if there are any, but this is harder.
        value n = if (is L ns) then [*ns.i.narrow<S>()] else ns;
        assert (is S|S[] n, is V x);
        V[] a = h(c, p);

        // local context
        LC lC = if (is S n) then
            LC(c.g, map { n -> L(*a) })
        else
            LC(c.g, map(zipEntries(n, a)));
        // return a continuation instead of actually
        // calling it here, to allow stack unwinding.
        return object satisfies Co {
            st() => x.vO(lC);
        };
    }
}

// symbol
class S(String n) satisfies V {
    // evaluate: resolve
    vO(X c) => c.v(this);
    // call is not allowed
    l(X c, V[] a) => nV;
    // equal if name is equal
    equals(O o) => if (is S o) then n == o.n else 1 < 0;
    hash => n.hash;
    string => n;
}

// integer
class I(sh Integer i) satisfies V {

    vO(X c) => this;
    l(X c, V[] a) => nV;
    equals(O o) => if (is I o) then i == o.i else 1 < 0;
    hash => i;
    b => !i.zero;
    string => i.string;
}

// argument handlers for functions or macros
V[] f(X c, V[] a) => [for (v in a) v.vF(c)];
V[] m(X c, V[] a) => a;

// build-in functions
// construct
L c(X c, V h, L t) => L(h, *t.i);
// head
V h(X c, L l) => l.i[0] else L();
// tail
V t(X c, L l) => L(*l.i.rest);
// subtract
I s(X c, I f, I s) => I(f.i - s.i);
// lessThan
I l(X c, I f, I s) => I(f.i < s.i then 1 else 0);
// equal
I e(X c, V v1, V v2) => I(v1 == v2 then 1 else 0);
// eval (returns potentially a continuation)
C v(X c, V v) => v.vO(c);

// build-in macros
// quote
V q(X c, V a) => a;
// if (also returns potentially a continuation)
C i(X c, V d, V t, V f) => d.vF(c).b then t.vO(c) else f.vO(c);
// define symbol in global context
S d(X c, S s, V x) => c.g.d(s, x.vF(c));

// buildin function or macro, made from a native function and an argument handler
class B<A>(F<C,A> nat, V[](X, V[]) h = f)
        satisfies V
        given A satisfies [X, V+] {
    vO(X c) => nV;
    string => nat.declaration.name;
    // this "apply" is a hack which breaks type safety ...
    // but it will be checked at runtime.
    l(X c, V[] a) => nat.apply(c, *h(c, a));
}

// define buildins
{<S->V>*} b => {
    S("c") -> B(`c`),
    S("h") -> B(`h`),
    S("t") -> B(`t`),
    S("s") -> B(`s`),
    S("l") -> B(`l`),
    S("e") -> B(`e`),
    S("v") -> B(`v`),
    S("q") -> B(`q`, m),
    S("i") -> B(`i`, m),
    S("d") -> B(`d`, m)
};

// parses a string into a list of expressions.
[V*] p(String inp) {
    // split string into tokens (retain separators, don't group them –
    // whitespace and empty strings will be sorted out later in the loop)
    value ts = inp.split(" \n()".contains, 1 < 0, 1 < 0);
    // stack of not yet finished nested lists, outer most at bottom
    va [[V*]*] s = [];
    // current list, in reverse order (because appending at the start is shorter)
    va [V*] l = [];
    for (t in ts) {
        if (t in " \n") {
            // do nothing for empty tokens
        } else if (t == "(") {
            // push the current list onto the stack, open a new list.
            s = [l, *s];
            l = [];
        } else if (t == ")") {
            // build a lisp list from the current list,
            // pop the latest list from the stack, append the created lisp list. 
            l = [L(*l.reversed), *(s[0] else [])];
            s = s.rest;
        } else if (exists i = parseInteger(t), i >= 0) {
            // append an integer to the current list.
            l = [I(i), *l];
        } else {
            // append a symbol to the current list.
            l = [S(t), *l];
        }
    }
    return l.reversed;
}

// Runs the interpreter.
// This handles input and output, calls the parser and evaluates the expressions.
sh void run() {
    va value u = "";
    while (exists l = process.readLine()) {
        u = u + l + "\n";
    }
    V[] e = p(u);
    // create global context
    X c = G(map(b));
    // iterate over the expressions, ...
    for (v in e) {
        // print("  '``v``' → ...");
        // ... evaluate each (fully) and print the result.
        print(v.vF(c));
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.