나는 그것을 평신도의 조건에 넣는 것을 찌르겠습니다.
구문 분석 트리 (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) 상향식 파서에서와 같이 확장하는 대신 축소 (생산이 "감소")되면 양쪽의 재귀는 문제가되지 않습니다.
::=
에서Expression
로 변경하고 첫 번째 항목 이후Term
에 동일한 작업을 수행하면||
더 이상 왼쪽 재귀 적이 지 않습니까? 그러나 만약 당신이 이후::=
에했지만 그렇지 않은||
경우에도 여전히 재귀 적입니까?