Lexers는 기본 파서의 성능 최적화로 사용되는 단순한 파서입니다. 우리가 어휘 분석기를 가지고 있다면, 어휘 분석기와 파서는 전체 언어를 설명하기 위해 함께 작동한다. 별도의 렉싱 단계가없는 파서는 때때로 "스캐너리스"라고합니다.
렉서가 없으면 파서는 문자별로 작동해야합니다. 파서는 모든 입력 항목에 대한 메타 데이터를 저장해야하고 모든 입력 항목 상태에 대해 테이블을 미리 계산해야 할 수 있기 때문에 큰 입력 크기에 대해 메모리를 사용할 수 없게됩니다. 특히 추상 구문 트리에서 문자마다 별도의 노드가 필요하지 않습니다.
문자별로 텍스트가 모호하기 때문에 처리하기가 훨씬 더 모호합니다. 규칙을 상상해보십시오 R → identifier | "for " identifier
. 여기서 식별자 는 ASCII 문자로 구성됩니다. 모호성을 피하려면 이제 어떤 대안을 선택해야할지 결정하기 위해 4 문자의 예견이 필요합니다. 어휘 분석기를 사용하면 파서가 IDENTIFIER 또는 FOR 토큰 (1-token lookahead)을 가지고 있는지 확인하기 만하면됩니다.
2 단계 문법.
Lexers는 입력 알파벳을보다 편리한 알파벳으로 변환하여 작동합니다.
스캐너가없는 파서는 문법 (N, Σ, P, S)을 설명합니다. 여기서 비 터미널 N은 문법 규칙의 왼쪽이고 알파벳 Σ는 예를 들어 ASCII 문자이며, 프로덕션 P는 문법의 규칙입니다. 시작 기호 S는 파서의 최상위 규칙입니다.
어휘 분석기는 이제 알파벳 a, b, c,… 이를 통해 주 파서는 이러한 토큰을 알파벳으로 사용할 수 있습니다 : Σ = {a, b, c,…}. 어휘 분석기의 경우 이러한 토큰은 비 터미널이며 시작 규칙 S L 은 S L → ε | S | b S | c S | … 즉, 일련의 토큰. 렉서 문법의 규칙은 이러한 토큰을 생성하는 데 필요한 모든 규칙입니다.
성능상의 이점은 렉서의 규칙을 일반 언어 로 표현함으로써 얻을 수 있습니다 . 문맥없는 언어보다 훨씬 효율적으로 구문 분석 할 수 있습니다. 특히, 일반 언어는 O (n) 공간과 O (n) 시간에서 인식 될 수 있습니다. 실제로 코드 생성기는 이러한 어휘 분석기를 매우 효율적인 점프 테이블로 바꿀 수 있습니다.
문법에서 토큰 추출
예를 터치하려면 : digit
및 string
규칙이 문자별로 표시됩니다. 그것들을 토큰으로 사용할 수 있습니다. 나머지 문법은 그대로 유지됩니다. 다음은 규칙을 명확하게하기 위해 오른쪽 선형 문법으로 작성된 렉서 문법입니다.
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
string = '"' , string-rest ;
string-rest = '"' | STRING-CHAR, string-rest ;
STRING-CHAR = ? all visible characters ? - '"' ;
그러나 규칙적이기 때문에 일반적으로 정규식을 사용하여 토큰 구문을 표현합니다. 다음은 .NET 문자 클래스 제외 구문과 POSIX 문자 클래스를 사용하여 작성된 정규식으로서의 토큰 정의입니다.
digit ~ [0-9]
string ~ "[[:print:]-["]]*"
기본 파서의 문법에는 어휘 분석기가 처리하지 않은 나머지 규칙이 포함됩니다. 귀하의 경우에는 다음과 같습니다.
input = digit | string ;
어휘 분석기를 쉽게 사용할 수 없을 때.
언어를 디자인 할 때 우리는 일반적으로 문법을 명확하게 어휘 수준과 파서 수준으로 분리 할 수 있고, 어휘 수준이 정규 언어를 설명하도록주의를 기울입니다. 항상 가능하지는 않습니다.
언어를 포함시킬 때. 일부 언어에서는 코드를 문자열로 보간 할 수 있습니다 "name={expression}"
. 식 구문은 컨텍스트가없는 문법의 일부이므로 정규식으로 토큰화할 수 없습니다. 이 문제를 해결하기 위해 파서를 렉서와 다시 결합하거나와 같은 추가 토큰을 소개 STRING-CONTENT, INTERPOLATE-START, INTERPOLATE-END
합니다. 문자열에 대한 문법 규칙은 다음과 같습니다 String → STRING-START STRING-CONTENTS { INTERPOLATE-START Expression INTERPOLATE-END STRING-CONTENTS } STRING-END
. 물론 Expression에는 다른 문자열이 포함되어있어 다음 문제로 이어질 수 있습니다.
토큰이 서로를 포함 할 수있는 경우 C와 유사한 언어에서 키워드는 식별자와 구별 할 수 없습니다. 이것은 식별자보다 키워드의 우선 순위를 정함으로써 어휘 분석기에서 해결됩니다. 이러한 전략이 항상 가능한 것은 아닙니다. Line → IDENTIFIER " = " REST
여기서 나머지는 식별자처럼 보이지만 나머지는 줄 끝까지의 문자 인 구성 파일을 상상해보십시오 . 예제 줄은입니다 a = b c
. 어휘 분석기는 실제로 멍청하며 어떤 순서로 토큰이 발생할 수 있는지 모릅니다. 따라서 IDENTIFIER를 REST보다 우선시하면 어휘 분석기는 우리에게 줄 것이다 IDENT(a), " = ", IDENT(b), REST( c)
. IDENTIFIER보다 REST를 우선시하면, 어휘 분석기는 우리에게 그냥 줄 것이다 REST(a = b c)
.
이 문제를 해결하려면 어휘 분석기를 파서와 다시 결합해야합니다. 어휘 분석기를 게으르게함으로써 분리가 다소 유지 될 수있다. 파서는 다음 토큰이 필요할 때마다 어휘 분석기에서 토큰을 요청하고 허용 가능한 토큰 세트를 어휘 분석기에 알려준다. 효과적으로, 우리는 각 위치에 대한 렉서 문법에 대한 새로운 최상위 규칙을 만들고 있습니다. 여기에서 호출이 발생 nextToken(IDENT), nextToken(" = "), nextToken(REST)
하고 모든 것이 잘 작동합니다. 이를 위해서는 각 위치에서 허용 가능한 토큰의 전체 세트를 알고있는 파서가 필요합니다. 이는 LR과 같은 상향식 파서를 의미합니다.
어휘 분석기가 상태를 유지해야 할 때. 예를 들어 파이썬 언어는 중괄호가 아니라 들여 쓰기로 코드 블록을 구분합니다. 문법 내에서 레이아웃에 민감한 구문을 처리하는 방법이 있지만 이러한 기술은 파이썬에 과도합니다. 대신, 어휘 분석기는 각 줄의 들여 쓰기를 점검하고 새로운 들여 쓰기 블록이 발견되면 INDENT 토큰을 생성하고, 블록이 종료되면 DEDENT 토큰을 생성합니다. 이렇게하면 기본 문법이 단순 해 지므로 이제 토큰이 중괄호처럼 가장 할 수 있습니다. 그러나 어휘 분석기는 이제 현재 들여 쓰기 상태를 유지해야합니다. 이것은 어휘 분석기가 기술적으로 더 이상 일반 언어를 기술하지 않지만 실제로 상황에 맞는 언어를 기술한다는 것을 의미합니다. 운 좋게도이 차이는 실제로 관련이 없으며 Python의 어휘 분석기는 여전히 O (n) 시간 안에 작동 할 수 있습니다.