LL 파서

LL parser

컴퓨터 과학에서 LL 파서(왼쪽에서 오른쪽으로, 가장 왼쪽 파생)는 제한된 컨텍스트프리 언어를 위한 하향식 파서입니다.왼쪽에서 오른쪽으로 입력을 해석하여 문장의 왼쪽 파생됩니다.

LL 파서는 문장을 해석할 때 lookahead 토큰k개 사용하는 경우 LL(k) 파서라고 합니다.문법은 LL(k) 파서를 구성할 수 있는 경우 LL(k) 문법이라고 합니다.공식 언어는 LL(k) 문법이 있으면 LL(k) 언어라고 불립니다.LL(k) 언어 집합은 k 0 [1]0에 대해 LL(k+1) 언어의 집합으로 적절히 포함됩니다.결국 모든 컨텍스트가 없는 언어가 LL(k) 파서로 인식되는 것은 아닙니다.

LL 파서는 LL 정규 언어를 해석하는 경우 LL 정규([clarification needed][2][3][4]LLR)라고 불립니다.LLR 문법의 클래스는 k마다 모든 LL(k) 문법을 포함합니다.모든 LLR 문법에 대해 선형 [citation needed]시간으로 문법을 해석하는 LLR 파서가 존재합니다.

두 가지 명명 특이치 파서 유형은 LL(*)과 LL(무한)입니다.파서는 LL(*)/LL(finite) 해석 전략을 사용하는 경우 LL(*)/LL(finite)이라고 불립니다.[5][6] LL(*) 및 LL(finite) 파서는 기능적으로 PEG 파서와 더 유사합니다.LL(finite) 파서는 임의의 LL(k) 문법을 예측 및 예측 비교의 양으로 최적으로 해석할 수 있습니다.LL(*) 전략으로 해석할 수 있는 문법의 클래스는 구문 술어와 의미 술어의 사용으로 인해 일부 문맥에 민감한 언어를 포함하지만 식별되지 않았습니다.LL(*) 파서는 TDPL [7]파서로 간주하는 것이 좋습니다.일반적인 오해와 달리 LL(*) 파서는 일반적으로 LLR이 아니며, 구조에 의해 평균 성능 저하(선형 시간에 대해 초선형) 및 최악의 경우 성능 저하(선형 시간에 대해 지수)가 보장됩니다.

LL 문법, 특히 LL(1) 문법은 매우 실용적입니다.이러한 문법의 파서는 구축이 용이하며, 많은 컴퓨터 언어가 이러한 [8]이유로 LL(1)이 되도록 설계되어 있기 때문입니다.LL 파서는 LR 파서와 유사한 테이블 기반 [citation needed]파서입니다.LL 문법은 재귀 강하 파서로 구문 분석할 수도 있습니다.웨이트와 구스에 따르면,[9] LL(k) 문법은 스턴스와 루이스에 의해 도입되었다.[10]

개요

주어진 문맥이 없는 문법에 대해 파서는 가장 왼쪽의 파생어를 찾으려고 합니다.의 예를 다음에 제시하겠습니다

w ( ( +) + ) w)+i 가장 왼쪽 파생은 다음과 같습니다.

일반적으로 가장 왼쪽에 있는 비단말기를 확장하는 규칙을 선택할 때 여러 가지 방법이 있습니다.위의 예의 스텝2에서는 파서는 규칙2 또는 규칙3 중 어느 쪽을 적용할지를 선택해야 합니다.

효율적으로 하기 위해서는 파서는 가능한 한 백트래킹 없이 결정적으로 이 선택을 할 수 있어야 합니다.일부 문법의 경우 읽지 않은 입력(읽지 않고)을 훔쳐봄으로써 이를 수행할 수 있습니다.이 예에서는 파서가 다음 읽지 않은 기호가(\임을 알고 있는 경우 사용할 수 있는 올바른 규칙은 2뿐입니다.

으로 L (k) \ LL ( )파서는 k\ ksymbols 를 앞쪽으로 볼 수 있습니다.단, 문법에 L (k) \ LL( ) 인식되는 k \ k}파서가 존재하는지 여부를 판별할 수 없습니다.k(\ k에 대해 L)\k) 파서에서는 할 수 없지만 +1)\ LL에서는 인식할 수 없는 언어가 있습니다.

위의 분석을 사용하여 다음과 같은 정식 정의를 내릴 수 있습니다.

G G 문맥이 없는 문법, 1로 .GG L이라고 합니다.단, 가장 왼쪽의 2개의 파생물에 대해서만 L이라고 합니다.

stringu{ k k the the the {\ u { display style k} the v { }의 프레픽스는 \\ style 합니다.

이 정의에서 S S 시작 이고 A A 터미널 이외의 기호입니다.이미 w\ w 그러나 않은u u 및 v는 터미널 문자열입니다. α(\β(\(\ 터미널과 비터미널의 모든 문자열을 나타냅니다(공백일 수 있음).프리픽스 길이는 룩어헤드버퍼 사이즈에 대응하고 정의상 이 버퍼는 다른 워드의 파생어 2개를 구별하기에 충분합니다.

파서

L ( ){LL( 파서는 결정론적 푸시다운 오토마톤으로, k \ k 입력 기호를 읽지 않고 엿볼 수 있습니다.버퍼와 입력 알파벳 모두 크기가 유한하기 때문에 이 피크 기능은 룩어헤드 버퍼 내용을 유한 상태 공간에 저장함으로써 에뮬레이트할 수 있습니다.결과적으로, 이것은 자동화를 더 강력하게 만드는 것이 아니라, 편리한 추상화이다.

스택 알파벳은 N ( \ \ \ \ 。여기서 다음과 같이 입력합니다.

  • N 비터미널 집합입니다.
  • \Sigma }단말기(입력) 심볼 세트.특수 입력 종료(EOI $ \ \displaystyle \$ } 。

파서 스택에는 처음에 EOI 위에 기호[SS가 포함되어 있습니다.동작 중에 파서는 스택 상단의 X X 반복해서 바꿉니다.

  • {\X N}이고 규칙 α {\\to \alpha 인 경우 {\displaystyle X \alpha를 사용합니다.
  • 에서는 \가 있는 경우 X 스택에서 팝업됩니다.이 경우 입력 x(\ X 읽혀지고 X X가 표시된다.

스택에서 마지막으로 삭제되는 기호가 EOI일 경우 해석은 성공합니다.자동은 빈 스택을 통해 수신합니다.

상태 및 전이 함수는 명시적으로 지정되어 있지 않습니다.대신 보다 편리한 해석 테이블을 사용하여 지정(생성)됩니다.이 테이블은 다음 매핑을 참조해당 매핑은 다음과 같습니다.

  • 행: 스택 상단 X X
  • 열: \ w \ k} 미리 버퍼 내용 보기
  • 셀: X {\ X \ {\ 규칙 번호

파서가 유효한 천이를 실행할 수 없는 경우 입력은 거부됩니다(빈 셀).테이블을 보다 콤팩트하게 하기 위해 일반적으로 단말기가 아닌 행만 표시됩니다.이는 단말기의 조작이 동일하기 때문입니다.

구체적인 예

세우다

LL(1) 파서의 동작을 설명하기 위해 다음과 같은 작은 LL(1) 문법을 검토합니다.

  1. S → F
  2. S → ( S + F )
  3. F → a

및 다음 입력을 해석합니다.

( a + a )

문법을 위한 LL(1) 해석 테이블은 비말단 각각에 대한 행과 각 단자(여기서 $로 표현되며 입력 스트림의 종료를 나타내기 위해 사용되는 특수 단자를 포함한다)에 대한 열을 가진다.

표의 각 셀은 최대 1개의 문법 규칙(숫자로 식별)을 가리킬 수 있습니다.예를 들어 위의 문법에 대한 해석표에서는 비단말기 'S' 및 단말기 '()'의 셀이 규칙 번호2를 가리키고 있습니다.

( ) a + $
S 2 1
F 3

구문 분석 테이블을 구성하는 알고리즘에 대해서는 이후 섹션에서 설명하지만 먼저 구문 분석기가 구문 분석 테이블을 사용하여 입력을 처리하는 방법에 대해 설명합니다.

해석 절차

각 단계에서 파서는 입력 스트림에서 다음으로 사용 가능한 심볼과 스택에서 최상위 심볼을 읽습니다.입력 기호와 스택톱 기호가 일치하는 경우 파서는 둘 다 폐기하고 입력 스트림과 스택에 일치하지 않는 기호만 남습니다.

따라서 파서는 첫 번째 단계에서 입력 기호 '()'와 스택 탑 기호 'S'를 읽습니다.구문 분석 테이블 명령은 입력 기호 '('로 시작하는 열과 스택 탑 기호 'S'로 시작하는 행에서 가져옵니다. 이 셀에는 규칙 (2)를 적용하도록 파서에 지시하는 '2'가 포함되어 있습니다.파서는 스택에서 'S'를 삭제하고 ')', 'F', '+', 'S', 'S'()를 눌러 스택에서 'S'를 'S'로 고쳐 써야 합니다.이것에 의해, 룰 번호 2가 출력에 써집니다.그 후 스택은 다음과 같이 됩니다.

[ ( , S , + , F , $ )

두 번째 단계에서 파서는 입력 스트림과 스택에서 '('를 삭제합니다.이는 일치하기 때문입니다.이것으로 스택은 다음과 같이 됩니다.

[ S , + , F , $ ]

이제 파서는 입력 스트림에 'a'가 있고 스택 상단으로 'S'가 있습니다.해석 테이블은 문법 규칙 (1)을 적용하여 규칙 번호1을 출력 스트림에 쓰도록 지시합니다.스택은 다음과 같습니다.

[ F , + , F , $ ]

이제 파서는 입력 스트림에 'a'가 있고 스택 상단으로 'F'가 있습니다.해석 테이블은 문법 규칙 (3)을 적용하여 규칙 번호3을 출력 스트림에 쓰도록 지시합니다.스택은 다음과 같습니다.

[a, +, F, $]

이제 파서는 입력 스트림에 'a'가 있고 스택 상단에 'a'가 있습니다.이들은 동일하기 때문에 입력 스트림에서 삭제되고 스택의 상부에서 팝됩니다.그런 다음 파서는 입력 스트림에 '+'가 있고 '+'는 스택의 맨 위에 있습니다. 즉, 'a'와 같이 스택에서 팝업되어 입력 스트림에서 제거됩니다.그 결과 다음과 같습니다.

[F, , $]

다음 3단계에서 파서는 스택의 'F'를 'a'로 대체하고 규칙 번호 3을 출력 스트림에 쓰고 스택과 입력 스트림 모두에서 'a'''를 제거합니다.따라서 파서는 스택과 입력 스트림 모두에서 '

로 끝납니다.

이 경우 파서는 입력 문자열을 받아들인 것을 보고하고 다음 규칙 번호 목록을 출력 스트림에 씁니다.

[ 2, 1, 3, 3 ]

이것은 실제로 입력 문자열의 왼쪽 끝에 파생되는 규칙 목록입니다.다음은 예를 제시하겠습니다.

S ( S + F ) ( F + F ) → ( a + F ) → ( a + a )

C++에서의 파서 실장

다음으로 샘플 언어의 테이블베이스 LL 파서의 C++ 실장을 나타냅니다.

#실패하다 <iostream> #실패하다 <맵> #실패하다 <스택>  열거하다 기호 {  // 기호:  // 단자 기호:  TS_L_PARES, // (  TS_R_PARES, // )  TS_A,  // a  TS_PLUS, // +  TS_EOS,  // $. 이 경우 '\0'에 해당합니다.  TS_INVALID, // 잘못된 토큰   // 터미널 이외의 기호:  NTS_S,  // S  NTS_F  // F };  /* 유효한 토큰을 해당 터미널 기호로 변환합니다. */ 기호 렉서( c) {  전환하다 (c)  {   사례. '(':  돌아가다 TS_L_PARES;   사례. ')':  돌아가다 TS_R_PARES;   사례. 'a':  돌아가다 TS_A;   사례. '+':  돌아가다 TS_PLUS;   사례. '\0': 돌아가다 TS_EOS; // 스택의 끝: $ terminal 기호   체납:   돌아가다 TS_INVALID;  } }  인트 주된(인트 argc,  **argv) {  사용. 네임스페이스 표준;   한다면 (argc < > 2)  {   외치다 << > "기능:\n\tll '(a+a)' << > ;   돌아가다 0;  }   // LL 파서 테이블, <non-terminal, terminal> 쌍을 액션에 매핑  지도< > 기호, 지도< >기호, 인트> > 테이블;  스택< >기호> ss; // 기호 스택   *p; // 입력 버퍼   // 기호 스택 초기화  ss.밀다(TS_EOS); // 단말기, $  ss.밀다(NTS_S);  // 비단말기, S   // 기호 스트림 커서 초기화  p = &argv[1][0];   // 해석 테이블 설정  테이블[NTS_S][TS_L_PARES] = 2;  테이블[NTS_S][TS_A] = 1;  테이블[NTS_F][TS_A] = 3;   하는 동안에 (ss.크기() > 0)  {   한다면 (렉서(*p) == ss.정상())   {    외치다 << > "일치 기호: " << > 렉서(*p) << > ;    p++;    ss.();   }   또 다른   {    외치다 << > '규칙" << > 테이블[ss.정상()][렉서(*p)] << > ;    전환하다 (테이블[ss.정상()][렉서(*p)])    {     사례. 1: // 1. S → F      ss.();      ss.밀다(NTS_F); // F      브레이크.;      사례. 2: // 2. S → ( S + F )      ss.();      ss.밀다(TS_R_PARES); // )      ss.밀다(NTS_F);  // F      ss.밀다(TS_PLUS); // +      ss.밀다(NTS_S);  // S      ss.밀다(TS_L_PARES); // (      브레이크.;      사례. 3: // 3. F → a      ss.();      ss.밀다(TS_A); // a      브레이크.;      체납:      외치다 << > "디폴트된 테이블" << > ;      돌아가다 0;    }   }  }   외치다 << > "오류 해석" << > ;   돌아가다 0; } 

Python에서의 파서 구현

# 모든 상수는 0부터 색인화됩니다. 용어 = 0 규칙. = 1  터미널 수 T_LPAR = 0 T_RPAR = 1 T_A = 2 T_PLUS = 3 T_END = 4 T_INVALID = 5  비종단수 N_S = 0 N_F = 1  # 해석 테이블 테이블 = [[ 1, -1, 0, -1, -1, -1],          [-1, -1, 2, -1, -1, -1]]  규칙. = [[(규칙., N_F)],          [(용어, T_LPAR), (규칙., N_S), (용어, T_PLUS), (규칙., N_F), (용어, T_RPAR)],          [(용어, T_A)]]  스택 = [(용어, T_END), (규칙., N_S)]  방어하다 어휘 분석(입력 문자열):     인쇄물("렉시컬 분석")     토큰 = []     위해서 c  입력 문자열:         한다면 c   == "+": 토큰.추가하다(T_PLUS)         엘리프 c == "(": 토큰.추가하다(T_LPAR)         엘리프 c == ")": 토큰.추가하다(T_RPAR)         엘리프 c == "a": 토큰.추가하다(T_A)         또 다른: 토큰.추가하다(T_INVALID)     토큰.추가하다(T_END)     인쇄물(토큰)     돌아가다 토큰  방어하다 구문 분석(토큰):     인쇄물(구문 분석)     위치 = 0     하는 동안에 (스택) > 0:         (타이프, ) = 스택.()         상품권 = 토큰[위치]         한다면 타이프 == 용어:             한다면  == 상품권:                 위치 += 1                 인쇄물("팝", )                 한다면 상품권 == T_END:                     인쇄물("입력 허용")             또 다른:                 인쇄물("입력 시 잘못된 조건: ", 상품권)                 브레이크.         엘리프 타이프 == 규칙.:             인쇄물("값", , "실패", 상품권)             규칙. = 테이블[][상품권]             인쇄물('규칙", 규칙.)             위해서 r  반전했다(규칙.[규칙.]):                 스택.추가하다(r)         인쇄물('스택」, 스택)  입력 문자열 = (a+a) 구문 분석(어휘 분석(입력 문자열)) 

언급

예에서 알 수 있듯이 파서는 스택의 상단이 비터미널인지 터미널인지 특수 기호 $인지에 따라 다음 3가지 유형의 절차를 수행합니다.

  • 상단이 비터미널일 경우 파서는 이 비터미널과 입력 스트림의 기호를 기반으로 해석 테이블에서 검색됩니다.이 기호는 스택 상의 비터미널을 대체하기 위해 사용해야 하는 문법의 규칙입니다.규칙 번호는 출력 스트림에 기록됩니다.해석 테이블이 이러한 규칙이 없음을 나타내는 경우 파서는 오류를 보고하고 중지합니다.
  • 상단이 터미널인 경우 파서는 이를 입력 스트림의 기호와 비교하고 동일한 경우 둘 다 제거됩니다.동일하지 않은 경우 파서는 오류를 보고하고 중지합니다.
  • 맨 위가 $이고 입력 스트림에 $도 있으면 파서는 입력을 성공적으로 구문 분석했다고 보고하고 그렇지 않으면 오류를 보고합니다.어느 경우든 파서는 정지합니다.

파서가 정지할 때까지 이 단계를 반복한 후 입력을 완전히 해석하여 출력 스트림에 최좌측 파생 데이터를 기록하거나 오류를 보고합니다.

LL(1) 구문 분석 테이블 구성

파싱 테이블을 작성하기 위해서는 파서가 스택의 맨 위에 비터미널A와 입력 스트림에 기호a를 검출했을 경우 어떤 문법 규칙을 선택해야 하는지 확립해야 합니다.이러한 규칙은 A → w 형식이어야 하며 w에 대응하는 언어는 a로 시작하는 문자열을 하나 이상 가져야 한다는 것을 쉽게 알 수 있습니다.이를 위해 여기에서는 Fi(w)로 표기된 첫 번째 w 세트를 w의 일부 문자열 선두에 있는 터미널 세트로 정의합니다.빈 문자열도 w에 속하는 경우에는 if를 추가합니다.규칙1 A → w1, …, Anwn 문법이 주어지면, 다음과 같이 모든 규칙에 대해 Fi(wi)와 Fi(Ai)를 계산할 수 있습니다.

  1. 집합으로 모든 Fi(Ai)를 초기화하다
  2. 모든 규칙i A → wi 대해 Fi(wi)를 Fi(Ai)에 추가합니다. 여기서 Fi는 다음과 같이 정의됩니다.
    • Fi(aw') = 모든 터미널 a에 대해 { a }
    • Fi(Aw') = Fi(A)가 아닌 θ의 모든 비단자 A에 대한 Fi(A)
    • Fi(Aw') = (Fi(A) \ { ε }) }) ( Fi(w')가 Fi(A)에서 in인 모든 비단말기 A에 대해
    • Fi(표준) = { }
  3. 모든 규칙i A → wi 대해 Fi(wi)를 Fi(Ai)에 더한다.
  4. 모든 파이 세트가 동일하게 유지될 때까지 2단계와 3단계를 수행합니다.

그 결과 다음 시스템에 대한 최소 고정점 솔루션이 됩니다.

  • 규칙 A ) Fi(w) → w
  • 단말 a에 대해 Fi(a) for { a }
  • Fi(w01 w) fi Fi(w0) · Fi(w1), 모든 단어0 w 1 w
  • Fi()) { {}}

여기서 U와 V의 경우, 잘린 곱은 U V { ( v) : : V ( \ U \ V = \ { ( ) : : u \ U \ V\} )로 정의되며, w:1은 접두어 중 첫 번째 길이 또는 w를 나타냅니다.

유감스럽게도 첫 번째 집합은 구문 분석 테이블을 계산하기에 충분하지 않습니다.이는 규칙의 오른쪽 w가 최종적으로 빈 문자열로 다시 작성될 수 있기 때문입니다.따라서 파서는 θ가 Fi(w)에 있고 입력 스트림에서 A 뒤에 오는 기호를 발견하면 A → w 규칙도 사용해야 합니다.따라서 여기에서는 Fo(A)로 표기된 AFollow-set도 필요합니다.이것은 시작 기호에서 도출할 수 있는 일련의 기호αAaβ가 존재하도록 단자 a 세트로 정의됩니다.입력 스트림의 종료를 나타내는 특수 단말로서 $를 사용하고, 스타트 심볼로서 S를 사용합니다.

문법의 비말단에 대한 팔로우 세트를 계산하는 방법은 다음과 같습니다.

  1. { $ }을(를) 사용하여 Fo(S)를 초기화하고 빈 집합으로 다른 모든 Fo(Ai)를 초기화합니다.
  2. A → wAwi' 형식j 규칙이 있는 경우,
    • 단자 a가 Fi(w')일 경우 Fo(Ai)에 a를 추가합니다.
    • θ가 Fi(w')일 경우 Fo(Aj)를 Fo(Ai)에 추가한다.
    • w'의 길이가 0인 경우 Fo(Aj)를 Fo(Ai)에 추가합니다.
  3. 모든 Fo 세트가 동일하게 유지될 때까지 2단계를 반복합니다.

이를 통해 다음 시스템에 최소 고정점 솔루션을 제공할 수 있습니다.

  • Fo(S) { {$}
  • B 형식의 규칙에 대한 Fo(A) ) Fi(w) · Fo(B) → ...오.

이제 구문 분석 테이블의 어디에 표시되는 규칙을 정확하게 정의할 수 있습니다.T[A, a]가 비단말기 A 및 단말기 A의 테이블 내의 엔트리를 나타내는 경우

T[A,a]에는 규칙 A → if 및 only 다음과 같은 경우에만 포함됨
a는 Fi(w) 또는
θFi(w)이고 aFo(A)입니다.

동등:T[A, a]는 각 θ Fi(wFo(A)에 대해 A → w 규칙을 포함한다.

테이블이 셀마다 최대 1개의 규칙을 포함하는 경우 파서는 어떤 규칙을 사용해야 하는지 항상 인식하고 있기 때문에 역추적 없이 문자열을 해석할 수 있습니다.바로 이 경우에 문법을 LL(1) 문법이라고 한다.

LL(k) 구문 분석 테이블 구성

LL(1) 파서의 구성은 다음과 같이 수정하여 k > 1의 LL(k)에 적용할 수 있습니다.

  • 잘린 곱은 U { ( v) : : V ( \ U \ V = \ { ( ) : 1 : \ U , v \ in V \} )로 정의됩니다.여기서 w:k는 k 또는 w의 길이가 k보다 작은 단어의 첫 번째 length-k 프리픽스를 나타냅니다.
  • Fo(S) = {$}k
  • LL(1)에 대해 주어진 Fi 구성의 2단계에서도 Fi(α) = Fi(α) { Fi(β)를 적용하십시오.
  • Fo 구성의 2단계에서는 A → wAwi'j 대해 Fo(Ai)에 Fi(w') {\} Fo(Aj)를 더하기만 하면 됩니다.

여기서 입력에는 k개의 끝부분 $가 부가되어 k개의 선행 컨텍스트를 완전히 고려합니다.이 접근방식은 §의 특별한 경우를 배제하고 LL (1)의 경우에도 동일하게 적용할 수 있습니다.

1990년대 중반까지 파서 테이블은 최악의 경우 k 단위지수 크기를 가질 것이기 때문에 LL(k) 구문 분석(k > 1)은 [citation needed]실용적이지 않다고 널리 알려져 있었다.이 인식은 파서의 최악의 동작을 트리거하지 않고 LL(k) 파서에 의해 많은 프로그래밍 언어를 효율적으로 구문 분석할 수 있다는 것이 증명된 1992년경 Purdue 컴파일러 구성 도구 집합의 출시 이후 점차적으로 변화했습니다.게다가 경우에 따라서는 LL 해석은 무제한의 룩어헤드에서도 실현 가능합니다.반면 yacc와 같은 기존 파서 생성기에서는 LALR(1) 파서 테이블을 사용하여 고정된 원토큰 룩어헤드 LR 파서를 구축합니다.

갈등들

도입부에서 설명한 바와 같이 LL(1) 파서는 문맥이 없는 문법의 특수한 경우인 LL(1) 문법을 가진 언어를 인식하지만 LL(1) 파서는 문맥이 없는 모든 언어를 인식할 수 없습니다.LL(1) 언어는 LR(1) 언어의 적절한 서브셋이며, LL(1) 언어는 컨텍스트가 없는 모든 언어의 적절한 서브셋입니다.문맥이 없는 문법이 LL(1) 문법이 되기 위해서는 이 섹션에서 설명하는 것과 같이 특정 충돌이 발생하지 않아야 합니다.

용어.

A를 비단말기로 합니다.FIRST(A)는 A에서 파생된 문자열의 첫 번째 위치에 나타날 수 있는 단자 집합입니다. FOLLOW(A)는 다음을 [11]합친 것입니다.

  1. FIRST(B) 여기서 B는 생산 규칙의 오른쪽에서 A 바로 에 오는 모든 비단말기입니다.
  2. 추종(B) 여기서 B는 B → wA 형식의 규칙 머리글이다.

LL(1)의 경합

LL(1) 경합에는 주로 다음 두 가지 유형이 있습니다.

첫 번째/첫 번째 충돌

같은 비단말기에 대한 두 가지 다른 문법 규칙의 첫 번째 집합이 교차합니다.LL(1) FIRST/FIRST 경합의 예:

S -> E E 'a' E -> 'b' »

FIRST(E) = {b, }} 및 FIRST(E a) = {b, a}이므로 테이블을 그릴 때 생산 규칙 S의 단자 b에서 충돌이 발생합니다.

특수한 경우: 왼쪽 재귀

왼쪽 재귀는 모든 대안과 FIRST/FIRST 경합을 일으킵니다.

E -> E '+' 용어 alt1 alt2

첫 번째/팔로우 충돌

문법 규칙의 FIRST 세트와 FOLLOW 세트가 겹칩니다.FIRST 세트에 빈 문자열())이 있는 경우 어느 쪽을 선택해야 할지 알 수 없습니다.LL(1) 경합의 예를 다음에 나타냅니다.

S -> A 'b' A -> A > 'a' »

현재 A의 번째 집합은 {a, }}이고 다음 집합은 {a}입니다.

LL(1) 경합에 대한 솔루션

왼쪽 인수분해

일반적인 좌-요인은 "계수 배제"됩니다.

A - > X X Y Z

된다

A -> X B -> Y Z »

두 가지 대안이 FIRST/FIRST 충돌처럼 동일한 기호로 시작하는 경우 적용할 수 있습니다.

위의 FIRST/FIRST 충돌 예를 사용한 다른 예(더 복잡한 예)는 다음과 같습니다.

S -> E E 'a' E -> 'b' »

(단일 단말기가 아닌 단일 단말기로 변환됩니다).

S - > 'b' ' 'b' 'a' 'a'

왼쪽 팩터링을 통해

S -> 'b' E E -> 'a' »

대체

간접 또는 FIRST/FOLLOW 충돌을 제거하기 위해 규칙을 다른 규칙으로 대체합니다.이로 인해 FIRST/FIRST 경합이 발생할 수 있습니다.

좌측 재귀 제거

보세요.[12]

일반적인 방법은 왼쪽 재귀 제거를 참조하십시오.왼쪽 재귀 제거의 간단한 예는 다음과 같습니다.다음 프로덕션 규칙이 E에 재귀를 남겼습니다.

E -> E '+' T E -> T

이 규칙은 '+'로 구분된 T의 목록일 뿐입니다.정규 표현 형식 T('+' T)*.그래서 규칙은 다음과 같이 다시 쓰여질 수 있다.

E -> TZ Z -> '+' TZ Z -> »

이제 왼쪽 재귀도 없고 두 규칙 모두 충돌도 없습니다.

단, 모든 문맥이 없는 문법에 동등한 LL(k)-문법이 있는 것은 아닙니다.예를 들어 다음과 같습니다.

S - > A A - > 'a' A 'b' ε B - > 'a' B 'b'

이 문법에 의해 생성된 언어를 받아들이는 LL(k)-문법이 존재하지 않음을 알 수 있다.

「 」를 참조해 주세요.

메모들

  1. ^ Rosenkrantz, D. J.; Stearns, R. E. (1970). "Properties of Deterministic Top Down Grammars". Information and Control. 17 (3): 226–256. doi:10.1016/s0019-9958(70)90446-8.
  2. ^ Jarzabek, Stanislav; Krawczyk, Tomasz (1974). "LL-Regular Grammars". Instytutu Maszyn Matematycznych: 107–119.
  3. ^ Jarzabek, Stanislav; Krawczyk, Tomasz (Nov 1975). "LL-Regular Grammars". Information Processing Letters. 4 (2): 31–37. doi:10.1016/0020-0190(75)90009-5.
  4. ^ David A. Poplawski (Aug 1977). Properties of LL-Regular Languages (Technical Report). Purdue University, Department of Computer Science.
  5. ^ Parr, Terence and Fisher, Kathleen (2011). "LL (*) the foundation of the ANTLR parser generator". ACM SIGPLAN Notices. 46 (6): 425–436. doi:10.1145/1993316.1993548.{{cite journal}}: CS1 maint: 여러 이름: 작성자 목록(링크)
  6. ^ Belcak, Peter (2020). "The LL(finite) parsing strategy for optimal LL(k) parsing". arXiv:2010.07874 [cs.PL].
  7. ^ Ford, Bryan (2004). "Parsing Expression Grammars: A Recognition-Based Syntactic Foundation". ACM SIGPLAN Notices. doi:10.1145/982962.964011.
  8. ^ Pat Terry (2005). Compiling with C# and Java. Pearson Education. pp. 159–164. ISBN 9780321263605.
  9. ^ William M. Waite and Gerhard Goos (1984). Compiler Construction. Texts and Monographs in Computer Science. Heidelberg: Springer. ISBN 978-3-540-90821-0. 여기: 5.3.2장, 페이지 121-127; 특히 페이지 123.
  10. ^ Richard E. Stearns and P.M. Lewis (1969). "Property Grammars and Table Machines". Information and Control. 14 (6): 524–549. doi:10.1016/S0019-9958(69)90312-X.
  11. ^ "LL Grammars" (PDF). Archived (PDF) from the original on 2010-06-18. Retrieved 2010-05-11.
  12. ^ 최신 컴파일러 설계, Grune, Bal, Jacobs 및 Langendoen

외부 링크