평신도의 관점에서, 왼쪽 재귀는 무엇입니까?


12

code.google.com의 한 페이지 에 따르면 "왼쪽 재귀"는 다음과 같이 정의됩니다.

왼쪽 재귀는 재귀 적 비 터미널을 의미하며, 자체를 포함하는 정서 양식을 생성 할 때 새 자체 사본이 프로덕션 규칙의 왼쪽에 나타납니다.

Wikipedia 는 두 가지 정의를 제공합니다.

  1. 문맥이없는 문법의 관점에서, r의 생산 ( '대안')에서 가장 왼쪽의 기호가 즉시 (직접 / 즉시 왼쪽 재귀) 또는 다른 비 터미널을 통해 비 말단 r은 왼쪽 재귀입니다. 정의 (간접 / 숨겨진 왼쪽 재귀)는 다시 r에 다시 씁니다.

  2. "비 말단 A를 찾을 수 있다면 문법은 왼쪽에서 재귀 적이다. 이것은 결국 자신을 왼쪽 기호로하여 형태를 도출 할 것이다."

나는 여기서 거의 언어 생성으로 시작하지 않고 여가 시간에 그것을하고 있습니다. 그러나 언어 파서를 선택하면이 파서가 왼쪽 재귀를 지원하는지 여부 또는 파서가 즉시 중앙에 나타나는 문제입니다 . "문학적 형태"와 같은 용어를 찾아 보면 전문 용어의 추가 목록 만 나오지만 "왼쪽"재귀의 구별은 거의 매우 단순해야합니다. 번역 부탁드립니다.

답변:


21

일치 R여부를 확인하기 위해 R먼저 일치 여부를 찾아야하는 경우 규칙 이 재귀 적 R입니다. 이는 R일부 자체 제작의 첫 번째 용어로 직접 또는 간접적으로 나타날 때 발생 합니다.

산만 함을 피하기 위해 덧셈과 곱셈만으로 수학 표현을위한 장난감 버전의 문법을 상상해보십시오.

Expression ::= Multiplication '+' Expression
            || Multiplication

Multiplication ::= Term '*' Term
                 || Term

Term ::= Number | Variable

작성된 바와 같이 여기에는 왼쪽 재귀가 없습니다.이 문법을 재귀 강하 파서에 전달할 수 있습니다.

그러나 다음과 같이 작성했다고 가정하십시오.

Expression ::= Expression '*' Expression
            || Expression '+' Expression
            || Term

Term ::= Number | Variable

이것은 문법이며 일부 파서는 그에 대처할 수 있지만 재귀 적 하강 파서와 LL 파서는 할 수 없습니다. 규칙은 자체 로 Expression시작 하기 때문 Expression입니다. 재귀 하강 파서에서 이것이 실제로 입력을 소비하지 않고 무한 재귀로 이어지는 이유는 분명해야합니다.

규칙이 직접 또는 간접적으로 참조되는지는 중요하지 않습니다. 경우 A그와 시작 대안을 가지고 B, 그리고 B대안을 가지고 그 시작과 함께 A다음 AB둘 간접적으로 왼쪽 재귀, 그들의 매칭 기능 파서 재귀 - 하강에 끝없는 상호 재귀로 이어질 것입니다.


따라서 두 번째 예에서에서 첫 번째 항목을 ::=에서 Expression로 변경하고 첫 번째 항목 이후 Term에 동일한 작업을 수행하면 ||더 이상 왼쪽 재귀 적이 지 않습니까? 그러나 만약 당신이 이후 ::=에했지만 그렇지 않은 ||경우에도 여전히 재귀 적입니까?
Panzercrisis

많은 파서가 왼쪽에서 오른쪽으로 이동하여 모든 기호에서 멈추고 그 자리에서 재귀 적으로 평가한다고 말하는 것처럼 들립니다. 이 경우, 첫 번째 Expression함께 전환했다 Term후 모두 ::=첫 번째 후 ||, 모든 것이 잘 될 것입니다; 빨리 때문에 이상, 그것은 어느 쪽도 무언가로 실행됩니다 Number아니고는 Variable, 따라서 뭔가를 결정할 수있는 것은 아니다 없습니다 Expression... 더 실행하지 않고
Panzercrisis

...하지만 여전히 그 중 하나가로 시작 Expression하면 잠재적으로 아닌 것이 아닌 것을 발견하고 Term모든 것이 Expression끝났 는지 계속 확인합니다 . 이거예요?
Panzercrisis

1
@Panzercrisis는 어느 정도입니다. LL, LR 및 재귀 하강 파서의 의미를 찾아야합니다.
hobbs September

이것은 기술적으로 정확하지만 충분히 간단하지는 않습니다 (레이맨의 용어). 실제로 LL 파서는 일반적으로 재귀를 감지하고 피할 수있는 능력을 가지고 있으며 (실제로는 대부분의 프로그래밍 언어에 문법이 정의되어 있다는 사실뿐만 아니라 재귀를 피할 수 있음) 무한 재귀를 피하는 방법.

4

나는 그것을 평신도의 조건에 넣는 것을 찌르겠습니다.

구문 분석 트리 (AST가 아니라 파서의 방문 및 입력 확장)에 대해 생각하면 왼쪽 재귀로 인해 왼쪽과 아래쪽으로 자라는 트리가 생성됩니다. 올바른 재귀는 정반대입니다.

예를 들어, 컴파일러의 일반적인 문법은 항목 목록입니다. 문자열 목록 ( "red", "green", "blue")을 가져 와서 구문 분석 할 수 있습니다. 몇 가지 방법으로 문법을 쓸 수 있습니다. 다음 예제는 각각 직접 왼쪽 또는 오른쪽 재귀입니다.

arg_list:                           arg_list:
      STRING                              STRING
    | arg_list ',' STRING               | STRING ',' arg_list 

이 파싱을위한 나무들 :

         (arg_list)                       (arg_list)
          /      \                         /      \
      (arg_list)  BLUE                  RED     (arg_list)
       /       \                                 /      \
   (arg_list) GREEN                          GREEN    (arg_list)
    /                                                  /
 RED                                                BLUE

재귀 방향으로 어떻게 성장하는지 주목하십시오.

이것은 실제로 문제가되지 않습니다. 파서 도구가 처리 할 수 ​​있다면 왼쪽 재귀 문법을 작성하는 것이 좋습니다. 상향식 파서는 잘 처리합니다. 따라서 더 현대적인 LL 파서는 가능합니다. 재귀 문법의 문제는 재귀가 아니며 파서를 진행하지 않고 재귀하거나 토큰을 소비하지 않고 재귀하는 것입니다. 재귀 할 때 항상 최소한 1 개의 토큰을 소비하면 결국 구문 분석의 끝에 도달합니다. 왼쪽 재귀는 소비하지 않고 재귀하는 것으로 정의되며 이는 무한 루프입니다.

이 제한은 순진한 하향식 LL 파서 (재귀 하강 파서)를 사용하여 문법을 구현하는 구현 세부 사항입니다. 왼쪽 재귀 문법을 고수하려면 재귀 전에 적어도 하나의 토큰을 소비하도록 프로덕션을 다시 작성하여 처리 할 수 ​​있으므로 비생산적인 루프에 빠지지 않습니다. 재귀적인 문법 규칙의 경우, 문법을 한 수준의 미리보기로 평평하게하는 중간 규칙을 추가하여 재귀 적 프로덕션간에 토큰을 소비함으로써 규칙을 다시 작성할 수 있습니다. (참고 : 이것이 일반화 된 규칙을 지적하면서 문법을 다시 작성하는 유일한 방법 또는 선호되는 방법은 아닙니다.이 간단한 예에서 가장 좋은 방법은 올바른 재귀 양식을 사용하는 것입니다) 이 접근법은 일반화되었으므로 파서 생성기는 (이론적으로) 프로그래머와 관계없이 구현할 수 있습니다. 실제로, 나는 ANTLR 4가 이제 그렇게한다고 생각합니다.

위의 문법에서 왼쪽 재귀를 표시하는 LL 구현은 다음과 같습니다. 파서는 목록을 예측하는 것으로 시작합니다 ...

bool match_list()
{
    if(lookahead-predicts-something-besides-comma) {
       match_STRING();
    } else if(lookahead-is-comma) {
       match_list();   // left-recursion, infinite loop/stack overflow
       match(',');
       match_STRING();
    } else {
       throw new ParseException();
    }
}

실제로, 우리가 실제로 다루고있는 것은 "순진한 구현"입니다. 우리는 처음에 주어진 문장을 선언 한 다음, 그 예측에 대한 함수를 재귀 적으로 호출했으며, 그 함수는 순진하게 동일한 예측을 다시 호출합니다.

상향식 파서는 문장의 시작 부분을 재분석하지 않고 문장을 다시 조합하여 작동하기 때문에 어느 방향 으로든 재귀 규칙에 문제가 없습니다.

문법의 재귀는 위에서 아래로 생성하는 경우에만 문제가됩니다. 파서는 토큰을 소비 할 때 예측을 "확장"하여 작동합니다. LALR (Yacc / Bison) 상향식 파서에서와 같이 확장하는 대신 축소 (생산이 "감소")되면 양쪽의 재귀는 문제가되지 않습니다.

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