지난 며칠 동안 이러한 별도의 기술이 존재하는 이유와 그 강점과 약점이 무엇인지 더 잘 이해하기 위해 많은 연구를했습니다.
이미 존재하는 답변 중 일부는 차이점을 암시했지만 완전한 그림을 제공하지 않았으며 다소 의견이있는 것처럼 보였으 므로이 답변이 작성된 이유입니다.
이 설명은 길지만 중요합니다. 나와 함께 견디십시오 (또는 참을성이없는 경우 플로우 차트를 보려면 끝으로 스크롤하십시오).
파서 조합기와 파서 생성기의 차이점을 이해하려면 먼저 존재하는 다양한 파싱 유형의 차이점을 이해해야합니다.
파싱
파싱은 공식 문법에 따라 일련의 기호를 분석하는 프로세스입니다. (Computing Science에서) 구문 분석은 컴퓨터가 언어로 작성된 텍스트를 이해할 수 있도록하는 데 사용되며, 일반적으로 작성된 텍스트를 나타내는 구문 분석 트리 를 작성하고 트리의 각 노드에 서로 다른 쓰기 부분의 의미를 저장합니다. 그런 다음이 구문 분석 트리는 다른 컴파일러 (많은 컴파일러에서 사용)로 번역하고, 작성된 지침을 어떤 방식으로 (SQL, HTML) 직접 해석하여 Linter 와 같은 도구
가 작업을 수행 하는 등의 다양한 목적으로 사용될 수 있습니다. 등입니다. 때때로 구문 분석 트리가 명시 적으로 나타나지 않습니다.트리의 각 유형의 노드에서 수행해야하는 작업이 직접 실행됩니다. 이렇게하면 효율성이 향상되지만 수 중에는 여전히 암시적인 구문 분석 트리가 있습니다.
파싱은 계산 상 어려운 문제입니다. 이 주제에 대해 50 년이 넘는 연구가 있었지만 여전히 배울 것이 많다.
대략적으로 말하면 컴퓨터가 입력을 구문 분석 할 수있게하는 네 가지 일반적인 알고리즘이 있습니다.
- LL 파싱. 컨텍스트가없는 하향식 구문 분석입니다.
- LR 파싱. 컨텍스트가없는 상향식 구문 분석입니다.
- PEG + Packrat 파싱.
- 얼리 파싱.
이러한 유형의 구문 분석은 매우 일반적인 이론적 설명입니다. 서로 다른 트레이드 오프로 물리적 시스템에서 이러한 각 알고리즘을 구현하는 여러 가지 방법이 있습니다.
LL 및 LR은 컨텍스트없는 문법 만 볼 수 있습니다 (즉, 작성된 토큰 주위의 컨텍스트는 사용 방법을 이해하는 데 중요하지 않습니다).
PEG / Packrat 구문 분석 및 Earley 구문 분석 은 훨씬 덜 사용됩니다. Earley 구문 분석은 문법을 많이 처리 할 수 있다는 점에서 좋지만 (문맥이 필요없는 구문을 포함하여) 효율적이지 않습니다. 책 (4.1.1 항);이 주장이 여전히 정확한지 확실하지 않습니다).
구문 분석 표현식 문법 + 팩랫 구문 분석 은 비교적 효율적이며 LL 및 LR보다 많은 문법을 처리 할 수 있지만 아래에서 빠르게 다룰 수있는 모호성을 숨 깁니다.
LL (왼쪽에서 오른쪽으로, 가장 왼쪽에서 파생)
이것은 아마도 구문 분석에 대한 가장 자연스러운 방법 일 것입니다. 아이디어는 입력 문자열에서 다음 토큰을보고 트리 구조를 생성하기 위해 가능한 여러 재귀 호출 중 하나를 결정하는 것입니다.
이 트리는 'top-down'으로 만들어졌습니다. 즉, 우리는 트리의 루트에서 시작하여 입력 문자열을 통해 여행하는 것과 같은 방식으로 문법 규칙을 여행합니다. 또한 읽고있는 'infix'토큰 스트림에 해당하는 'postfix'를 구성하는 것으로 볼 수도 있습니다.
LL 스타일 구문 분석을 수행하는 파서는 지정된 원래 문법과 매우 유사하게 작성 될 수 있습니다. 따라서 이해하기 쉽고 디버깅 및 향상시킬 수 있습니다. 클래식 파서 조합기는 LL 스타일 파서를 구축하기 위해 함께 사용할 수있는 '레고 조각'에 지나지 않습니다.
LR (왼쪽에서 오른쪽으로, 가장 오른쪽 파생)
LR 구문 분석은 다른 방식으로 진행합니다. 상향식 : 각 단계에서 스택의 최상위 요소가 문법 목록과 비교되어 문법
의 상위 수준 규칙 으로 축소 될 수 있는지 확인합니다 . 그렇지 않으면 입력 스트림의 다음 토큰이 이동 되고 스택 위에 배치됩니다.
프로그램이 결국 문법에서 시작 규칙을 나타내는 스택에 단일 노드로 끝나는 경우 프로그램이 올바른 것입니다.
Lookahead
이 두 시스템 중 하나에서 어떤 선택을 할 것인지 결정하기 전에 입력에서 더 많은 토큰을 들여다 볼 필요가 있습니다. 이것은이다 (0)
, (1)
, (k)
또는 (*)
당신은 다음과 같은 두 가지 일반적인 알고리즘의 이름 뒤에 참조 - 구문 LR(1)
이나 LL(k)
. k
일반적으로 '문법에서 요구하는만큼'을 *
나타내는 반면, 일반적으로 '이 파서는 역 추적을 수행합니다'를 나타내며, 이는 더 강력하고 구현하기는 쉽지만 구문 분석을 계속할 수있는 파서보다 메모리 및 시간 사용량이 훨씬 높습니다. 선형 적으로.
LR 스타일 파서는 이미 '예측'을 결정할 때 스택에 많은 토큰을 가지고 있으므로 파견 할 정보가 더 있습니다. 즉, 동일한 문법에 대해 LL 스타일 파서보다 '선견지명'이 덜 필요한 경우가 많습니다.
LL 대 LR : Ambiguety
위의 두 가지 설명을 읽을 때 LL 스타일 구문 분석이 훨씬 더 자연스럽게 보이기 때문에 LR 스타일 구문 분석이 왜 존재하는지 궁금 할 것입니다.
그러나 LL 스타일 구문 분석에는 왼쪽 재귀 문제가 있습니다.
다음과 같은 문법을 작성하는 것은 매우 당연합니다.
expr ::= expr '+' expr | term
term ::= integer | float
그러나이 문법을 구문 분석 할 때 LL 스타일 구문 분석기는 무한 재귀 루프에 고정됩니다. expr
규칙 의 가장 왼쪽 가능성을 시도 하면 입력을 사용하지 않고이 규칙으로 다시 반복됩니다.
이 문제를 해결하는 방법이 있습니다. 가장 간단한 방법은 이러한 재귀가 더 이상 발생하지 않도록 문법을 다시 작성하는 것입니다.
expr ::= term expr_rest
expr_rest ::= '+' expr | ϵ
term ::= integer | float
(여기서 ϵ 는 '빈 문자열'을 나타냅니다)
이 문법은 이제 재귀 적입니다. 즉시 읽기가 훨씬 어렵습니다.
실제로, 왼쪽 재귀는 다른 많은 단계들 사이에서 간접적으로 발생할 수 있습니다 . 이것은 찾아보기 어려운 문제입니다. 그러나 해결하려고하면 문법을 읽기가 더 어려워집니다.
Dragon Book의 섹션 2.5는 다음과 같이 말합니다.
우리는 갈등이있는 것처럼 보입니다 : 한편으로는 번역을 용이하게하는 문법이 필요하고, 다른 한편으로는 파싱을 용이하게하는 상당히 다른 문법이 필요합니다. 해결책은 쉬운 번역을 위해 문법으로 시작하고 구문 분석을 용이하게하기 위해 신중하게 변환하는 것입니다. 왼쪽 재귀를 제거함으로써 예측 재귀 하강 변환기에 사용하기에 적합한 문법을 얻을 수 있습니다.
LR 스타일 파서는 상향식에서 트리를 작성하므로이 왼쪽 재귀 문제는 없습니다.
그러나 위와 같은 문법을 LR 스타일 파서 (종종 유한 상태 오토 마톤 (Finite-State Automaton ) 로 구현) 로의 정신 번역은 종종
수백 또는 수천 개의 상태가 있기 때문에 수행하기가 매우 어렵고 오류가 발생하기 쉽습니다. 고려해야 할 상태 전이. LR 스타일 파서는 일반적으로 '컴파일러 컴파일러'라고도하는 파서 생성기에 의해 생성 되는 이유 입니다.
모호성을 해결하는 방법
위의 왼쪽 재귀 모호성을 해결하는 두 가지 방법을 보았습니다. 1) 구문을 다시 작성하십시오. 2) LR 파서를 사용하십시오.
그러나 해결하기 어려운 다른 종류의 모호함이 있습니다. 두 개의 서로 다른 규칙이 동시에 똑같이 적용될 수 있다면 어떨까요?
몇 가지 일반적인 예는 다음과 같습니다.
LL 스타일 및 LR 스타일 파서는 모두 이것에 문제가 있습니다. 연산자 우선 순위를 도입하여 산술 연산 식 구문 분석 문제를 해결할 수 있습니다. 비슷한 방식으로, Dangling Else와 같은 다른 문제는 우선 순위 행동 하나를 고르고 해결함으로써 해결할 수 있습니다. (예를 들어 C / C ++에서 매달려있는 댕글 링은 항상 가장 가까운 'if'에 속합니다).
이에 대한 또 다른 '해결책'은 PEG (Parser Expression Grammar)를 사용하는 것입니다. 이는 위에서 사용한 BNF 문법과 유사하지만 모호한 경우 항상 '첫 번째를 선택하십시오'. 물론 이것은 실제로 문제를 '해결'하는 것이 아니라 모호성이 실제로 존재한다는 사실을 숨 깁니다. 최종 사용자는 파서가 어떤 선택을하는지 알지 못하므로 예기치 않은 결과가 발생할 수 있습니다.
일반적으로 문법에 모호성이 없는지 알 수없는 이유를 포함하여이 게시물보다 훨씬 더 자세한 정보가 더 많이 포함되어 있습니다. 이것이 의미하는 멋진 블로그 기사 LL 및 LR이 있습니다. 도구는 어렵다 . 나는 그것을 강력히 추천 할 수있다. 그것은 내가 지금 이야기하고있는 모든 것을 이해하는 데 많은 도움이되었습니다.
50 년간의 연구
그러나 삶은 계속됩니다. 유한 상태 오토 마톤으로 구현 된 '정상적인'LR 스타일 파서는 수천 개의 상태 + 전환이 종종 필요하며 프로그램 크기에 문제가있는 것으로 나타났습니다. 따라서 SLR ( Simple LR ) 및 LALR (Look-ahead LR )과 같은 변형 을 작성하여 다른 기술을 결합하여 오토 마톤을 더 작게 만들어 파서 프로그램의 디스크 및 메모리 공간을 줄였습니다.
또한 위에 나열된 모호성을 해결하는 또 다른 방법은 모호한 경우 두 가지 가능성을 모두 유지하고 구문 분석 하는 일반화 된 기법 을 사용 하는 것입니다. '올바른'하나)뿐만 아니라 두 가지 모두를 반환하는 경우 (모두가 있음을 나타내는 방식으로)
흥미롭게도, 일반화 된 LR 알고리즘 이 기술 된 후에, 유사한 접근법이 일반화 된 LL 파서 를 구현하는데 사용될 수 있다는 것이 밝혀졌습니다 . 이는 모호한 문법에 대해 유사하게 빠른 ($ O (n ^ 3) $ 시간 복잡성, $ O (n) 단순한 (LA) LR 파서보다 더 많은 부기를 가지고 있지만 완전히 모호하지 않은 문법의 경우 $는 더 높은 상수 인자를 의미하지만 파서는 훨씬 더 자연스러운 재귀 강하 (위에서 아래로) 스타일로 작성 될 수 있습니다 작성하고 디버그합니다.
파서 조합기, 파서 생성기
그래서이 긴 박람회에서 우리는 이제 질문의 핵심에 도달하고 있습니다.
파서 조합기와 파서 생성기의 차이점은 무엇이며, 언제 다른 것보다 사용해야합니까?
그들은 정말 다른 종류의 짐승입니다.
파서 콤비 네이터 는 사람들이 하향식 파서를 작성하고 이들 중 많은 부분이 공통점 이 있음을 깨달았 기 때문에 만들어졌습니다 .
파서 생성기 는 사람들이 LL 스타일 파서 (예 : LR 스타일 파서)에 문제가없는 파서를 작성하려고했기 때문에 만들어졌습니다. 일반적인 예로는 (LA) LR을 구현하는 Yacc / Bison이 있습니다.
흥미롭게도 요즘에는 풍경이 다소 흐릿합니다.
GLL 알고리즘 과 함께 작동하는 파서 조합기 를 작성 하여 모든 종류의 하향식 파싱과 마찬가지로 읽기 / 이해할 수있는 고전적인 LL 스타일 파서의 모호한 문제를 해결합니다.
LL 스타일 파서를 위해 파서 생성기를 작성할 수도 있습니다. ANTLR은이를 정확히 수행하고 다른 휴리스틱 (Adaptive LL (*))을 사용하여 클래식 LL 스타일 파서의 모호성을 해결합니다.
일반적으로 원래 문법을 '내부'LR 형식으로 변환하기 때문에 LR 파서 생성기를 만들고 문법에서 실행되는 (LA) LR 스타일 파서 생성기의 출력을 디버깅하기가 어렵습니다. 반면에, Yacc에 / 들소와 같은 도구는 최적화의 많은 년을했고, 많은 사람들이 지금으로 고려하는 것이 의미 야생에서 사용을 많이 보았다 구문 분석을 수행하는 방법과 새로운 접근을 향한 회의적입니다.
어떤 것을 사용해야하는지는 문법이 얼마나 어려운지, 파서가 얼마나 빠를 지에 달려 있습니다. 문법에 따라 이러한 기술 중 하나 (다른 기술의 구현)가 더 빠르거나, 메모리 공간이 더 작거나, 디스크 공간이 더 작거나, 다른 것보다 더 확장 가능하거나 디버그하기가 더 쉽습니다. 마일리지가 다를 수 있습니다.
참고 : Lexical Analysis의 주제에 대해
어휘 분석은 파서 조합기와 파서 생성기 모두에 사용될 수 있습니다. 아이디어는 구현하기 매우 쉽고 (따라서 빠른) 소스 코드에 대한 첫 번째 패스를 수행하여 공백, 주석 등을 제거하고 아마도 토큰 화하는 'dumb'파서를 갖는 것입니다. 당신의 언어를 구성하는 다른 요소들
주요 이점은이 첫 번째 단계로 실제 파서를 훨씬 간단하게 만들 수 있다는 것입니다. 가장 큰 단점은 별도의 변환 단계가 있다는 것입니다. 예를 들어 공백을 제거하기 때문에 행 및 열 번호가있는 오류보고가 더 어려워집니다.
결국 어휘 분석기는 또 다른 파서 일 뿐이며 위의 기술 중 하나를 사용하여 구현할 수 있습니다. 단순하기 때문에 종종 주요 파서보다 다른 기술이 사용되며, 예를 들어 여분의 '렉서 생성기'가 존재합니다.
Tl; Dr :
다음은 대부분의 경우에 적용 가능한 플로우 차트입니다.
javac
,, Scala)에 선호되는 구현 형태입니다 . 그것은 당신에게 어떤 최근 몇 년 동안 ... (좋은 오류 메시지를 생성에 도움이 내부 파서 상태를 통해 대부분의 제어, 제공