렉서 vs 파서
어휘 분석기와 파서는 이론상 그렇게 다른가?
코딩 공포 , 또 다른 블로그 게시물 인 정규 표현식을 싫어하는 것이 유행처럼 보입니다 .
그러나 인기있는 어휘 기반 도구 인 pygments , geshi 또는 prettify 는 모두 정규식을 사용합니다. 그들은 아무것도 렉스 것 같습니다 ...
렉싱이 충분한시기, 언제 EBNF가 필요합니까?
이 렉서에서 생성 된 토큰을 들소 또는 앤 트러 파서 생성기와 함께 사용한 사람이 있습니까?
파서와 어휘 분석기의 공통점 :
입력에서 알파벳의 기호 를 읽습니다 .
- 힌트 : 알파벳은 반드시 글자 일 필요는 없습니다. 그러나 파서 / 렉서가 이해하는 언어의 원자 기호이어야 합니다.
- 어휘 분석기의 기호 : ASCII 문자.
- 구문 분석기의 기호 : 문법의 터미널 기호 인 특정 토큰.
그들은이 상징 들을 분석하고 그들이 이해 한 언어 의 문법 과 일치 시키려고 노력합니다 .
- 여기에 실제 차이점이 있습니다. 자세한 내용은 아래를 참조하십시오.
- 렉서가 이해하는 문법 : 일반 문법 (Chomsky 레벨 3).
- 구문 분석기가 이해하는 문법 : 문맥없는 문법 (Chomsky의 레벨 2).
그들은 찾은 언어 조각 에 의미 (의미)를 첨부 합니다.
- 렉서는 분류하여 의미 첨부 할 어휘를 특정로 (입력으로부터 기호의 문자열) 토큰 . 예 :이 모든 어휘는 :
*
,==
,<=
,^
는 C / C ++ 렉서가 토큰 "연산자"로 분류됩니다. - 파서는 입력 (문장)의 토큰 문자열을 특정 비 터미널 로 분류 하고 구문 분석 트리를 작성하여 의미를 부여 합니다. 예 : 모든 토큰 문자열 :
[number][operator][number]
,[id][operator][id]
,[id][operator][number][operator][number]
는 C / C ++ 파서에 의해 비단 '표현'으로 분류됩니다.
- 렉서는 분류하여 의미 첨부 할 어휘를 특정로 (입력으로부터 기호의 문자열) 토큰 . 예 :이 모든 어휘는 :
인식 된 요소에 몇 가지 추가 의미 (데이터)를 첨부 할 수 있습니다.
- 어휘 분석기는 적절한 숫자를 구성하는 문자 시퀀스를 인식하면 이진수로 변환하여 "숫자"토큰으로 저장할 수 있습니다.
- 마찬가지로 구문 분석기는 표현식을 인식하면 해당 값을 계산하고 구문 트리의 "expression"노드와 함께 저장할 수 있습니다.
그들은 모두 자신이 알고있는 언어 의 적절한 문장 을 출력에 만들어 낸다 .
- 렉서는 생산 토큰 이며, 문장 의 정규 언어 가 인식합니다. 각 토큰은 내부 구문 (레벨 2는 아니지만 레벨 3)을 가질 수 있지만 출력 데이터와이를 읽는 토큰에는 중요하지 않습니다.
- 파서는 구문 트리를 생성하는데, 구문 트리는 인식 하는 문맥이없는 언어 의 문장 을 나타냅니다 . 전체 문서 / 소스 파일이 적절한 문장 이므로 일반적으로 전체 문서 / 소스 파일에 대해 하나의 큰 트리입니다 . 그러나 파서가 출력에서 일련의 구문 트리를 생성하지 못한 이유는 없습니다. 예를 들어 일반 텍스트에 붙어있는 SGML 태그를 인식하는 파서 일 수 있습니다. 따라서 SGML 문서를 일련의 토큰으로 토큰 화 합니다 .
[TXT][TAG][TAG][TXT][TAG][TXT]...
보다시피 파서와 토크 나이 저는 공통점이 많다. 한 파서는 다른 파서의 토크 나이저 일 수 있으며, 한 언어의 문장이 다른 상위 레벨의 알파벳 기호와 같은 방식으로 입력 토큰을 자체 알파벳의 기호 (토큰은 일부 알파벳의 기호 임)로 읽는다. 언어. 예를 들어, *
와 -
알파벳의 상징 M
( "모스 부호 기호"로), 당신은 모스 부호로 인코딩 된 문자 이러한 점과 라인의 문자열을 인식하는 파서를 구축 할 수 있습니다. 언어 "모스 코드"의 문장이 될 수 토큰 다른 파서, 이러한 토큰언어의 원자 기호 (예 : "영어 단어"언어). 이 "영어 단어"는 "영어 문장"언어를 이해하는 일부 상위 레벨 구문 분석기의 토큰 (알파벳 기호) 일 수 있습니다. 그리고이 모든 언어는 문법의 복잡성 만 다릅니다 . 더 이상 없습니다.
그렇다면이 "Chomsky의 문법 수준"은 어떻습니까? Noam Chomsky는 문법을 복잡도에 따라 4 단계로 분류했습니다.
레벨 3 : 정규 문법
그들은 즉, 그들은 단지 알파벳의 기호로 구성 할 수 있습니다, 정규 표현식을 사용 (a
,b
), 자신의 회씩 연결은 (ab
,aba
,bbb
. ETD), 또는 대안 (예를 들면a|b
).
NFA (Nondeterministic Finite Automaton) 또는보다 나은 DFA (Deterministic Finite Automaton)와 같은 FSA (Finite State Automata)로 구현할 수 있습니다.
정규 문법은 중첩 구문 , 예를 들어 올바르게 중첩 / 일치 괄호(()()(()()))
, 중첩 HTML / BBcode 태그, 중첩 블록 등을 처리 할 수 없습니다.이를 처리 하기위한 상태 오토마타는 무한히 많은 중첩 레벨을 처리하기 위해 무한히 많은 상태를 가져야하기 때문입니다.레벨 2 : 문맥없는 문법
구문 트리에 중첩, 재귀, 자기 유사 분기를 가질 수 있으므로 중첩 구조를 잘 처리 할 수 있습니다.
스택을 사용하여 상태 자동 상태로 구현할 수 있습니다. 이 스택은 구문의 중첩 수준을 나타내는 데 사용됩니다. 실제로는 일반적으로 컴퓨터의 프로 시저 호출 스택을 사용하여 중첩 수준을 추적하고 구문의 모든 비 터미널 심볼에 대해 재귀 적으로 호출되는 프로 시저 / 함수를 사용하는 하향식 재귀 하강 파서로 구현됩니다.
그러나 상황에 맞는 구문 으로는 처리 할 수 없습니다 . 예를 들어 표현식이x+3
있고 한 상황에서 이것은x
변수의 이름 이 될 수 있고 다른 상황에서는 함수의 이름이 될 수 있습니다.레벨 1 : 상황에 맞는 문법
레벨 0 : 무제한 문법
재귀 적으로 열거 가능한 문법이라고도합니다.
예, 이론과 구현면에서 매우 다릅니다.
Lexers는 언어 요소를 구성하는 "단어"를 인식하는 데 사용됩니다. 이러한 단어의 구조는 일반적으로 단순하기 때문입니다. 정규식은이 간단한 구조를 처리하는 데 매우 능숙하며, 어휘 분석기를 구현하는 데 사용되는 고성능 정규식 매칭 엔진이 있습니다.
파서는 언어 구의 "구조"를 인식하는 데 사용됩니다. 이러한 구조는 일반적으로 "정규 표현식"이 인식 할 수있는 것 이상으로, 그러한 구조를 추출하기 위해서는 "문맥에 민감한"파서가 필요하다. 상황에 맞는 파서는 작성하기가 어렵 기 때문에 "컨텍스트가없는"문법을 사용하고 구문 분석기 ( "기호 테이블"등)에 해킹을 추가하여 상황에 맞는 부분을 처리해야합니다.
렉싱이나 파싱 기술은 곧 사라질 것입니다.
그들은 스캐너가없는 GLR 파서에 의해 현재 탐색되고있는 바와 같이, "구문"기술을 사용하여 "단어"를 인식하기로 결정함으로써 통일 될 수 있다. 일반적으로 필요하지 않은 문제에 더 일반적인 기계를 적용 할 때 런타임 비용이 발생하며 일반적으로 오버 헤드로 비용을 지불합니다. 빈 사이클이 많은 경우 오버 헤드가 중요하지 않을 수 있습니다. 많은 텍스트를 처리하면 오버 헤드가 중요하며 고전적인 정규식 파서는 계속 사용됩니다.
렉싱이 충분한시기, 언제 EBNF가 필요합니까?
EBNF는 실제로 문법 의 힘 에 큰 도움이되지 않습니다 . 표준 Chomsky 's Normal Form (CNF) 문법 규칙에 비해 편의성 / 바로 가기 표기법 / "구문 설탕" 입니다. 예를 들어, EBNF 대안 :
S --> A | B
CNF에서 각 대체 생산물을 개별적으로 나열함으로써 달성 할 수 있습니다.
S --> A // `S` can be `A`,
S --> B // or it can be `B`.
EBNF의 선택적 요소 :
S --> X?
CNF에서 nullable 생산, 즉 빈 문자열 로 대체 할 수있는 생산 을 사용하여 달성 할 수 있습니다 (여기서는 빈 생산으로 표시됩니다. 다른 사람들은 엡실론 또는 람다 또는 교차 원을 사용합니다).
S --> B // `S` can be `B`,
B --> X // and `B` can be just `X`,
B --> // or it can be empty.
B
위 의 마지막 형태와 같은 형태의 제품 은 "지우기"라고합니다. 다른 제품에서 의미하는 것을 지울 수 있기 때문입니다 (제품은 다른 것이 아니라 빈 문자열).
EBNF에서 0 회 이상 반복 :
S --> A*
재귀 적 인 생산, 즉 어딘가에 자신을 포함시키는 것을 사용하여 망할 수 있습니다 . 두 가지 방법으로 수행 할 수 있습니다. 첫 번째는 왼쪽 재귀입니다 (Top-Down 재귀 하강 파서는 구문 분석 할 수 없으므로 일반적으로 피해야합니다).
S --> S A // `S` is just itself ended with `A` (which can be done many times),
S --> // or it can begin with empty-string, which stops the recursion.
빈 문자열 (궁극적으로)에 이어 0 개 이상의 A
s가 생성된다는 것을 알고 있으면 동일한 문자열 ( 동일한 언어는 아닙니다! )은 오른쪽 재귀를 사용하여 표현할 수 있습니다 .
S --> A S // `S` can be `A` followed by itself (which can be done many times),
S --> // or it can be just empty-string end, which stops the recursion.
그리고 +
EBNF에서 한 번 이상 반복 할 때 :
S --> A+
하나 A
를 제외 *
하고 이전 과 같이 사용하여 수행 할 수 있습니다 .
S --> A A*
CNF에서 다음과 같이 표현할 수 있습니다 (여기서 올바른 재귀를 사용하고 다른 것을 스스로 운동으로 생각하십시오).
S --> A S // `S` can be one `A` followed by `S` (which stands for more `A`s),
S --> A // or it could be just one single `A`.
이제 정규식 (즉, 정규 문법 )에 대한 문법 을 터미널 기호로만 구성된 단일 EBNF 프로덕션으로 표현할 수 있는 문법으로 인식 할 수 있습니다. 보다 일반적으로 다음과 유사한 제작을 볼 때 정규 문법을 인식 할 수 있습니다.
A --> // Empty (nullable) production (AKA erasure).
B --> x // Single terminal symbol.
C --> y D // Simple state change from `C` to `D` when seeing input `y`.
E --> F z // Simple state change from `E` to `F` when seeing input `z`.
G --> G u // Left recursion.
H --> v H // Right recursion.
즉, 빈 문자열, 터미널 기호, 대체 및 상태 변경을위한 간단한 비 터미널 만 사용하고 반복을 반복하기 위해서만 반복을 사용합니다 (반복은 선형 재귀 -분기와 유사하지 않음). 이것보다 더 발전된 것은 없으며, 당신은 그것이 일반적인 구문인지 확신하고 그것을 위해 렉서와 함께 갈 수 있습니다.
그러나 구문에서 사소하지 않은 방식으로 재귀를 사용하는 경우 다음과 같이 트리와 유사하고 자체 유사하며 중첩 된 구조를 생성합니다.
S --> a S b // `S` can be itself "parenthesized" by `a` and `b` on both sides.
S --> // or it could be (ultimately) empty, which ends recursion.
어떤 식 으로든 단일 EBNF 프로덕션으로 해석 할 수 없기 때문에 정규 표현식으로 수행 할 수 없음을 쉽게 알 수 있습니다. 당신은 S
무기한 으로 대체하여 결국 양쪽에 또 다른 a
s를 추가 할 것 b
입니다. Lexers (보다 구체적으로 : Lexers가 사용하는 유한 상태 오토마타)는 임의의 수 (무한 수, 기억합니까?)로 계산할 수 없으므로 a
너무 많은 수와 균등하게 일치 하는 수를 알 수 없습니다 b
. 이와 같은 문법을 컨텍스트없는 문법 이라고 하며 (최소한) 파서가 필요합니다.
문맥없는 문법은 구문 분석하는 것으로 잘 알려져 있으므로 프로그래밍 언어의 구문을 설명하는 데 널리 사용됩니다. 그러나 더 있습니다. 독립적으로 동시에 더 많은 것을 계산할 때 더 일반적인 문법이 필요할 때가 있습니다. 예를 들어, 둥근 괄호와 대괄호를 사용할 수있는 언어를 설명하려고하지만 서로 올바르게 짝을 이루어야합니다 (중괄호와 괄호, 둥근 괄호). 이런 종류의 문법을 문맥 인식 이라고 합니다 . 왼쪽 (화살표 앞)에 둘 이상의 기호가 있음을 인식 할 수 있습니다. 예를 들면 다음과 같습니다.
A R B --> A S B
왼쪽에있는 이러한 추가 기호를 규칙을 적용하기위한 "컨텍스트"로 생각할 수 있습니다. 몇 가지 전제 조건이있을 수 등 사후 조건은 예를 들어, 위의 규칙을 대체 할 R
에 S
있지만,이 사이에 때만 A
와 B
사람들을 떠나 A
및 B
변경되지 않은 자신을. 이러한 종류의 구문은 완전한 튜링 머신이 필요하기 때문에 파싱하기가 실제로 어렵습니다. 완전히 다른 이야기이므로 여기서 끝내겠습니다.
요청에 따라 질문에 답변하기 (다른 답변에 나타나는 내용을 부적절하게 반복하지 않고)
허용 된 답변에서 제안한 것처럼 Lexers와 파서는 크게 다르지 않습니다. 둘 다 간단한 언어 형식을 기반으로한다 : 어휘 분석기를위한 정규 언어와 파서 (parsers)를위한 거의 항상 문맥이없는 (CF) 언어. 둘 다 상당히 간단한 계산 모델, 유한 상태 오토 마톤 및 푸시 다운 스택 오토 마톤과 관련이 있습니다. 정규 언어는 문맥이없는 언어의 특수한 경우이므로 다소 복잡한 CF 기술 로 어휘 분석기 를 만들 수 있습니다. 그러나 적어도 두 가지 이유로 좋은 아이디어는 아닙니다 .
프로그래밍의 기본 요점은 시스템 구성 요소가 가장 적합한 기술을 사용하여 생산, 이해 및 유지 관리가 용이해야한다는 것입니다. 기술은 과도하게 사용되어서는 안되며 (필요한 것보다 훨씬 더 복잡하고 비용이 많이 드는 기술을 사용) 기술의 힘을 제한해서는 안되므로 원하는 목표를 달성하기 위해 기술적 인 왜곡이 필요합니다.
그렇기 때문에 "정규 표현을 싫어하는 것이 유행처럼 보입니다". 그들은 많은 것을 할 수 있지만, 구현시 다양한 확장과 제한이 이론상의 단순성을 다소 감소 시킨다는 사실은 말할 것도없고, 그것을 달성하기 위해 읽을 수없는 코딩이 필요한 경우가 있습니다. Lexers는 일반적으로 그렇게하지 않으며 일반적으로 토큰을 구문 분석하는 간단하고 효율적이며 적절한 기술입니다. 토큰에 CF 파서를 사용하는 것은 가능하지만 과잉입니다.
어휘 분석기에 CF 형식을 사용하지 않는 또 다른 이유는 전체 CF 전력을 사용하고 싶을 수도 있기 때문입니다. 그러나 그것은 프로그램의 독서에 관한 구조적 문제를 일으킬 수 있습니다.
기본적으로 의미를 추출하는 프로그램 텍스트 구조의 대부분은 트리 구조입니다. 구문 분석 규칙에서 구문 분석 문장 (프로그램)이 생성되는 방법을 나타냅니다. 시맨틱 스는 구문 분석 규칙을 구성하여 구문 분석 트리를 작성하는 방식에서 구성 기술 (수학적 지향의 동질성)에 의해 파생됩니다. 따라서 트리 구조가 필수적입니다. 일반 세트 기반 렉서로 토큰이 식별된다는 사실은 상황을 변경하지 않습니다. 일반적으로 구성된 CF는 여전히 CF를 제공하기 때문에 (일반적인 변환기에 대해 매우 느슨하게 말하고 문자 스트림을 토큰 스트림으로 변환합니다).
그러나 CF로 구성된 CF (CF 변환기를 통해 ... 수학에 대해 죄송합니다)는 반드시 CF를 제공 할 필요는 없으며보다 일반적이지만 다루기 쉽지 않습니다. 따라서 CF는 사용할 수 있지만 어휘 분석기에 적합한 도구는 아닙니다.
정규 언어와 CF의 주요 차이점 중 하나는 정규 언어 (및 변환기)가 다양한 방식으로 거의 모든 형식주의와 매우 잘 구성되는 반면 CF 언어 (및 변환기)는 그 자체가 아니라도 (몇 가지 예외가 있음) 있습니다.
(일부 변환기는 일부 구문 오류 처리 기술의 공식화와 같은 다른 용도로 사용될 수 있습니다.)
BNF는 CF 문법을 표현하기위한 특정 구문입니다.
EBNF는 규칙적인 표기법을 사용하여 BNF 문법의 더 간결한 버전을 제공하는 BNF의 구문 설탕입니다 . 항상 동등한 순수 BNF로 변환 될 수 있습니다.
그러나 정규 표기법은 종종 어휘 요소의 구조에 해당하는 구문의 이러한 부분을 강조하기 위해 EBNF에서 자주 사용되며 나머지는 곧바로 BNF로 표시됩니다. 그러나 절대적인 규칙은 아닙니다.
요약하면, 토큰의 간단한 구조는 일반 언어의 간단한 기술로 더 잘 분석되는 반면 (프로그램 구문의) 언어의 트리 지향 구조는 CF 문법으로 더 잘 처리됩니다.
나는 또한 AHR의 답변을 볼 것을 제안 합니다.
그러나 이것은 왜 나무가 문제인가?
트리는 구문을 지정하기위한 좋은 기초입니다.
그들은 텍스트에 간단한 구조를 제공합니다
위에 나타낸 바와 같이, 수학적으로 잘 이해 된 기술 (동질성을 통한 합성)을 사용하여, 그 구조에 기초하여 의미론과 텍스트를 연관시키는 것이 매우 편리하다. 수학 형식주의의 의미를 정의하는 기본 대수 도구입니다.
따라서 AST (Abstract Syntax Trees)의 성공으로 알 수 있듯이 좋은 중간 표현입니다. 많은 전문가가 사용하는 구문 분석 기술 (LL 또는 LR과 같은)이 CF 문법의 하위 집합에만 적용되므로 나중에 AST에서 수정되는 문법 왜곡이 발생하기 때문에 AST는 종종 구문 분석 트리와 다릅니다. CF 문법을 받아들이는보다 일반적인 파싱 기술 (동적 프로그래밍 기반)을 사용하면이를 피할 수 있습니다.
프로그래밍 언어가 CF가 아닌 문맥 인식 (CS)이라는 사실에 대한 진술은 임의적이며 논쟁의 여지가 있습니다.
문제는 구문과 의미의 분리가 임의적이라는 것입니다. 선언 또는 형식 일치 확인은 구문의 일부 또는 의미의 일부로 볼 수 있습니다. 자연어에서의 성별 및 숫자 합의도 마찬가지입니다. 그러나 복수 동의어가 단어의 실제 의미 론적 의미에 의존하여 구문과 잘 맞지 않는 자연어가 있습니다.
명칭 의미론에서 프로그래밍 언어에 대한 많은 정의는 의미론에서 선언 및 유형 검사를 배치합니다. 따라서 Ira Baxter 가 말한 것처럼 구문 분석기에 필요한 컨텍스트 감도를 얻기 위해 CF 파서가 해킹당하는 것은 상황에 대한 임의의 관점에 달려 있습니다. 일부 컴파일러에서는 핵으로 구성 될 수 있지만 반드시 그럴 필요는 없습니다.
또한 CS 파서 (여기 다른 답변에 사용 된 의미)가 구축하기가 어렵고 효율성이 떨어지는 것은 아닙니다. 또한 필요할 수있는 상황에 맞는 감도를 눈에 띄게 표현하기에는 부적절하다. 그리고 자연스럽게 프로그램의 의미를 도출하는 데, 즉 컴파일 된 코드를 생성하는 데 편리한 구문 구조 (예 : 구문 분석 트리)를 생성하지 않습니다.
컴파일러의 분석 부분이 일반적으로 어휘 분석 및 구문 분석 (구문 분석) 단계로 분리되는 데는 여러 가지 이유가 있습니다.
- 디자인의 단순성이 가장 중요한 고려 사항입니다. 어휘 분석과 구문 분석을 분리하면 종종 이러한 작업 중 하나 이상을 단순화 할 수 있습니다. 예를 들어 주석과 공백을 구문 단위로 처리해야하는 구문 분석기는 예입니다. 주석과 공백을 가정 할 수있는 것보다 상당히 복잡한 것은 어휘 분석기에 의해 이미 제거되었습니다. 새로운 언어를 디자인하는 경우 어휘와 구문상의 문제를 분리하면 전반적인 언어 디자인이 더 깨끗해질 수 있습니다.
- 컴파일러 효율성이 향상되었습니다. 별도의 어휘 분석기를 사용하면 구문 분석 작업이 아닌 어휘 작업 만 처리하는 특수 기술을 적용 할 수 있습니다. 또한 입력 문자를 읽기위한 특수 버퍼링 기술로 컴파일러 속도를 크게 높일 수 있습니다.
- 컴파일러 이식성이 향상되었습니다. 입력 장치 별 특성은 어휘 분석기로 제한 될 수 있습니다.
resource___ 컴파일러 알프레드 V. ABO 컬럼비아 대학 모니카 S. 램 스탠포드 대학 라비 세티 어바이어 제프리 D. Ullman은 스탠포드 대학 부산물 작성 (2 판)
참고 URL : https://stackoverflow.com/questions/2842809/lexers-vs-parsers
'development' 카테고리의 다른 글
RequireJS가 필수 스크립트를 캐시하지 못하도록 방지 (0) | 2020.03.16 |
---|---|
Java-Foo 유형의 엔 클로징 인스턴스에 액세스 할 수 없음 (0) | 2020.03.16 |
iframe 작업에서 부모 창 리디렉션 (0) | 2020.03.16 |
SSLError를 던지는 Python 요청 (0) | 2020.03.16 |
정적 / 동적 vs 강 / 약 (0) | 2020.03.16 |