나는 최근에 파서 (언어 / 문맥없는 문법을위한)가 어떻게 작동하는지 스스로 가르치려고 노력하고 있으며, 한 가지를 제외하고는 대부분 이해가되는 것 같습니다. 특히 두 가지 주요 알고리즘이 LL 파서 (스택 / 파싱 테이블 사용)와 재귀 하강 파서 (간단히 재귀 사용 ) 인 LL (k) 문법 에주의를 집중 하고 있습니다 .
내가 볼 수있는 한, 재귀 하강 알고리즘은 모든 LL (k) 문법과 그 이상에서 작동하는 반면 LL 파서는 모든 LL (k) 문법에서 작동합니다. 그러나 재귀 하강 파서는 구현할 LL 파서보다 훨씬 간단합니다 (LL 파서가 LR 파서보다 간단 하듯이).
그래서 제 질문은 알고리즘 중 하나를 사용할 때 발생할 수있는 장점 / 문제는 무엇입니까? 동일한 문법 세트에서 작동하고 구현하기가 더 까다 롭다는 점을 감안할 때 재귀 하강보다 LL을 선택하는 이유는 무엇입니까?
답변
LL은 일반적으로 재귀 하강보다 더 효율적인 구문 분석 기술입니다. 실제로 순진한 재귀 하강 파서는 최악의 경우 실제로 O (k ^ n) (여기서 n 은 입력 크기)입니다. 메모 화 ( Packrat 구문 분석기 를 생성하는)와 같은 일부 기술 은이를 개선 할뿐만 아니라 구문 분석기에서 허용하는 문법 클래스를 확장 할 수 있지만 항상 공간 절충이 있습니다. LL 파서는 (내가 아는 한) 항상 선형 시간입니다.
반대로, 재귀 하강 파서가 LL보다 더 큰 클래스의 문법을 처리 할 수 있다는 직감이 맞습니다. 재귀 하강은 LL (*) (즉, 무제한 예견) 인 모든 문법과 모호한 작은 문법 집합을 처리 할 수 있습니다 . 재귀 하강은 실제로 PEG 또는 Parser Expression Grammar (s) 의 직접 인코딩 된 구현이기 때문 입니다. 특히, 분리 연산자 ( a | b
)는 교환 적이 지 a | b
않습니다 b | a
. 즉, 같지 않음을 의미합니다 . 재귀 하강 파서는 각 대안을 순서대로 시도합니다. 경우에 따라서 a
입력과 일치하는 경우, 심지어는 succede 할 b
것이다 입력을 일치. 이것은 매달려있는 것과 같은 고전적인 “가장 긴 일치”모호성을 허용합니다else
분리를 올바르게 주문하면 문제가 해결됩니다.
이 모든 것을 통해, 그 것이다 수 는 선형 시간에서 실행되도록 재귀 하강을 이용한 LL (K) 분석기를 구현하기. 이는 본질적으로 예측 세트를 인라인하여 각 구문 분석 루틴이 일정한 시간에 주어진 입력에 대해 적절한 생산을 결정하도록함으로써 수행됩니다. 불행히도 이러한 기술은 전체 문법 클래스가 처리되지 않도록합니다. 예측 파싱에 들어가면 댕글 링과 같은 문제 else
를 더 이상 쉽게 해결할 수 없습니다.
LL이 재귀 하강보다 선택되는 이유는 주로 효율성과 유지 관리의 문제입니다. 재귀 하강 파서는 구현하기가 훨씬 더 쉽지만 그들이 나타내는 문법이 선언적 형태로 존재하지 않기 때문에 일반적으로 유지하기가 더 어렵습니다. 대부분의 사소하지 않은 파서 사용 사례는 ANTLR 또는 Bison과 같은 파서 생성기를 사용합니다. 이러한 도구를 사용하면 알고리즘이 직접 인코딩 된 재귀 하강 또는 테이블 기반 LL (k)인지는 실제로 중요하지 않습니다.
흥미롭게도 재귀 하강 방식을 따라 직접 인코딩 된 구문 분석 알고리즘이지만 모든 LALR 문법을 처리 할 수있는 recursive-ascent 도 살펴볼 가치가 있습니다 . 또한 재귀 하강 파서를 함께 구성하는 기능적인 방법 인 파서 결합 자도 살펴볼 것입니다.