왜 왼쪽 재귀가 나쁜가요?


20

컴파일러 디자인에서 왜 문법에서 왼쪽 재귀를 제거해야합니까? 나는 그것이 무한한 재귀를 일으킬 수 있기 때문에 읽히고 있지만 올바른 재귀 문법에도 해당되지 않습니까?


2
일반적으로 컴파일러는 하향식 구문 분석을 사용합니다. 왼쪽 재귀가 있으면 파서는 무한 재귀로 이동합니다. 그러나 오른쪽 재귀에서 파서는 지금까지 문자열의 접두사를 볼 수 있습니다. 따라서 파생이 "너무 멀리"진행되었는지 확인할 수 있습니다. 물론 역할을 바꾸고 오른쪽에서 식을 해석하여 오른쪽 재귀를 나쁘게하고 왼쪽 재귀를 잘 할 수 있습니다.
Shaull

6
왼쪽 재귀는 컴퓨터에 16KB의 RAM이 있던 옛날에는 가장 일반적으로 사용되는 파서 생성기가이를 처리 할 수 ​​없었기 때문에 좋지 않습니다.
Andrej Bauer

답변:


15

왼쪽 재귀 문법 이 반드시 나쁜 것은 아닙니다. 이러한 문법은 스택을 사용하여 쉽게 구문 분석되어 LR 구문 분석기 에서와 같이 이미 구문 분석 된 구문을 추적합니다 .

CF 문법 의 왼쪽 재귀 규칙 은 다음과 같은 형식임을 기억하십시오.=(V,Σ,아르 자형,에스)

ααβ

의 소자 Vβ 의 요소 V Σ . (튜플의 전체 공식적인 정의를 참조하십시오 ( V , Σ , R , S가 ) 있습니다 ).αVβVΣ(V,Σ,아르 자형,에스)

일반적으로, 단말과 비 단자 시퀀스 사실이며하는 다른 규칙이있다 α α가 오른쪽에 나타나지는.βαα

문법 파서 (렉서로부터)에 의해 새로운 터미널이 수신 될 때마다,이 터미널은 스택 맨 위로 밀린다 :이 동작을 시프트 라한다 .

규칙의 오른쪽이 스택 맨 위에있는 연속 요소 그룹과 일치 할 때마다이 그룹은 새로 일치 된 구를 나타내는 단일 요소로 대체됩니다. 이 교체를 축소 라고합니다 .

올바른 재귀 문법을 사용하면 축소가 발생할 때까지 스택이 무한대로 커질 수 있으므로 구문 분석 가능성이 극적으로 제한됩니다. 그러나 왼쪽의 재귀는 컴파일러가 더 일찍 축소를 생성 할 수있게합니다 (사실 가능한 한 빨리). 자세한 정보는 Wikipedia 항목 을 참조하십시오.


변수를 정의하면 도움이 될 것입니다.
Andrew S

12

이 규칙을 고려하십시오.

example : 'a' | example 'b' ;

이제이 'b'규칙 과 같은 일치하지 않는 문자열을 일치시키려는 LL 구문 분석기를 고려하십시오 . 때문에 'a'일치하지 않는, 그것은 일치하려고합니다 example 'b'. 그러나 그렇게하려면 일치해야합니다 example... 처음에하려고했던 것입니다. 항상 동일한 토큰 스트림을 동일한 규칙과 일치 시키려고 시도하기 때문에 일치하는지 여부를 확인하기 위해 영원히 노력하지 않을 수 있습니다.

이를 방지하기 위해 오른쪽에서 구문 분석해야합니다 (이것은 아주 드문 경우이며 대신 문제를 재귀로 만들 것입니다). 인위적으로 허용되는 중첩의 양을 제한하거나 일치시킵니다. 재귀가 시작되기 전의 토큰이므로 항상 기본 사례가 있습니다 (즉, 모든 토큰이 소비되었지만 아직 완전히 일치하지 않는 경우). 올바른 재귀 규칙은 이미 세 번째 규칙을 수행하므로 동일한 문제가 없습니다.


3
파싱은 반드시 순진한 하향식 파싱이라고 가정합니다.
레이니어 포스트

나는 쉽게 피할 수있는 문제 인 다소 일반적인 구문 분석 방법의 함정을 강조하고 있습니다. 왼쪽 재귀를 처리하는 것은 확실히 가능 하지만, 그것을 유지하는 것은 그것을 사용할 수있는 파서 유형에 거의 항상 불필요하게 제한을 만듭니다.
cHao

예, 더 건설적이고 유용한 방법입니다.
reinierpost

4

(지금 까지이 질문이 꽤 오래되었다는 것을 알고 있지만 다른 사람들이 같은 질문을하는 경우를 대비하여 ...)

재귀 하강 파서의 맥락에서 묻고 있습니까? 예를 들어, grammar의 expr:: = expr + term | term경우 왜 다음과 같은 것입니까 (왼쪽 재귀) :

// expr:: = expr + term
expr() {
   expr();
   if (token == '+') {
      getNextToken();
   }
   term();
}

문제가 있지만 이것이 아닌 것입니까 (올바른 재귀)?

// expr:: = term + expr
expr() {
   term();
   if (token == '+') {
      getNextToken();
      expr();
   }
}

두 버전의 expr()통화 자체 처럼 보입니다 . 그러나 중요한 차이점은 컨텍스트, 즉 재귀 호출이 이루어질 때의 현재 토큰입니다.

왼쪽 재귀의 경우 expr()동일한 토큰으로 지속적으로 호출되며 진행되지 않습니다. 올바른 재귀의 경우 호출에 term()도달하기 전에 호출 및 PLUS 토큰의 일부 입력을 사용합니다 expr(). 따라서이 시점에서 재귀 호출은 용어를 호출 한 다음 if 테스트에 다시 도달하기 전에 종료 될 수 있습니다.

예를 들어, 2 + 3 + 4 구문 분석을 고려하십시오. 왼쪽 재귀 구문 분석기 expr()는 첫 번째 토큰에 붙어있는 동안 무한대로 호출 하는 반면, 오른쪽 재귀 구문 분석기는 expr()다시 호출하기 전에 "2 +"를 소비 합니다. 두 번째 통화 expr()는 "3 +"와 일치 expr()하고 4 개만 남은 통화 입니다. 4가 용어와 일치하고에 대한 추가 호출없이 구문 분석이 종료됩니다 expr().


2

Bison 매뉴얼에서 :

"모든 종류의 시퀀스는 왼쪽 재귀 또는 오른쪽 재귀를 사용하여 정의 할 수 있지만 항상 왼쪽 재귀를 사용해야합니다. 왼쪽 재귀 는 바운드 스택 공간이있는 여러 요소의 시퀀스를 구문 분석 할 수 있기 때문입니다. 오른쪽 재귀는 Bison 스택의 공간을 차지합니다. 규칙을 한 번이라도 적용하기 전에 모든 요소를 ​​스택으로 이동해야하기 때문에 시퀀스의 요소 수에 비례합니다. 이에 대한 자세한 설명은 Bison Parser 알고리즘을 참조하십시오. "

http://www.gnu.org/software/bison/manual/html_node/Recursion.html

따라서 파서의 알고리즘에 달려 있지만 다른 답변에서 언급했듯이 일부 파서는 왼쪽 재귀와 함께 작동하지 않을 수 있습니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.