컴파일 원칙: C++는 간단한 LL(1) 파서를 구성합니다.

다음은 개인 실험 코드입니다. 약간의 누락이 있을 수 있습니다. 교환 및 토론을 환영합니다 www~   

참조 자료: 컴파일 원리 실험 2: LL(1) 구문 분석기 - Chris-Zhang - 博客园

프로젝트 파일: main.cpp, LLdefine.h, LLfun.cpp

프로젝트 소스 코드에 대한 리소스 보기: LLone.zip(단순 LL(1) 파서)-C/C++ 설명서 리소스-CSDN 다운로드

프로그램 기능: 사용자 입력 문법, 직접 왼쪽 재귀 제거(간접 왼쪽 재귀 제외), FIRST 및 FOLLOW 세트 계산, 분석 테이블 생성, 사용자가 입력한 문장이 문법에 부합하는지 판단.

작업 결과:

Please enter VN:E F T
Please enter the start VN:E
Please enter VT:+ * ( ) i
Please enter Grammar(empty:@,end with #):
E->E+T|T
T->T*F|F
F->(E)|i
#

*****check Left Recursion*****
direct Recursion is found in 'E->E+T'.Grammar has changed.
direct Recursion is found in 'T->T*F'.Grammar has changed.

*****First Set*****
E : ( i
F : ( i
T : ( i
E' : + @
T' : * @
+ : +
* : *
( : (
) : )
i : i

*****Follow Set*****
E : # )
F : # ) * +
T : # ) +
E' : # )
T' : # ) +

*****Analytical Table*****
          +         *         (         )         i         #
E                             E->TE'              E->TE'
F                             F->(E)              F->i
T                             T->FT'              T->FT'
E'        E'->+TE'                      E'->@               E'->@
T'        T'->@     T'->*FT'            T'->@               T'->@

Please enter a sentence:98+99+90

*****Analyse the sentence*****
Analytical Stack    Rest Sentence       Grammar Used
#E                  i+i+i#              E->TE'
#'ET                i+i+i#              T->FT'
#'E'TF              i+i+i#              F->i
#'E'Ti              i+i+i#
#'E'T               +i+i#               T'->@
#'E                 +i+i#               E'->+TE'
#'ET+               +i+i#
#'ET                i+i#                T->FT'
#'E'TF              i+i#                F->i
#'E'Ti              i+i#
#'E'T               +i#                 T'->@
#'E                 +i#                 E'->+TE'
#'ET+               +i#
#'ET                i#                  T->FT'
#'E'TF              i#                  F->i
#'E'Ti              i#
#'E'T               #                   T'->@
#'E                 #                   E'->@
#                   #
Success:The sentence belongs to the grammar.

1. 데이터 구조 설명

LL(1) 파서를 구축하는 것은 다섯 부분으로 나눌 수 있습니다. 첫 번째 부분은 사용자가 문법을 생성하기 위해 문법의 몇 가지 규칙을 입력하는 것이고 두 번째 부분은 왼쪽 재귀를 감지하는 것입니다(여기서는 직접 왼쪽 재귀만 감지됨). 직접 왼쪽 재귀가 인식되면 문법이 변환됩니다. 세 번째 부분은 FIRST 세트와 FOLLOW 세트를 풀고, 네 번째 부분은 LL(1) 분석 테이블을 구성하는 것이고, 다섯 번째 부분은 사용자가 문장을 입력하고 분석 스택과 나머지 문자열 스택을 구성하고 분석하는 것입니다. 문장의 합법성.

따라서 단말 기호, 비단말 기호, 문법 규칙 등의 데이터를 캡슐화하여 저장함과 동시에 위에서 언급한 기능을 구현하기 위한 멤버 함수를 설정하기 위해 클래스 LLone을 설정한다. 동시에, 문법 규칙의 저장을 용이하게 하기 위해 규칙의 좌우 부분을 별도로 저장하는 구조 문법이 설정되어 후속 프로그램 기능 구현에 편리합니다. 구조 및 클래스 정의는 다음과 같습니다.

#pragma once
#include<iostream>
#include<set>
#include<string>
#include<map>
#include<vector>
#include<stack>
#include<iomanip>
using namespace std;

//文法结构体
struct grammar {   
	string left;		    //左部
	vector<string> right;   //右部
};

//LL(1)类
class LLone {
public:
	vector<char>vt;//终结符
	vector<string>vn;//非终结符
	set<char>vt_s;
	set<string>vn_s;
	string start;

	string usr_in;

	vector<grammar>production;//产生式集合

	map<string, set<char>>first;//first集
	map<string, set<char>>follow;//follow集
	set<string>toEmpty;//vn能否推出空
	set<string>isFirst;//是否已求过first集


	map<string, int>vnTnum;//vn在分析表中下标
	map<char, int>vtTnum;//vt在分析表中下标
	vector < vector<string>> table;//分析表

	stack<string>ana; //分析栈
	stack<char>sen;   //余留输入串

	void getInfo();//获取vt,vn,grammar
	void checkLRE();//检测并消除左递归

	void allFirst();//获取全部First集(外部接口)
	int getFirst(string vn_f);//递归获取First集

	void allFollow();//获取全部Follow集(外部接口)
	
	void printFirst();//打印First集
	void printFollow();//打印Follow集

	void makeTable();//构建LL(1)分析表
	void printTable();//打印分析表

	void usrInput();//获取用户输入的句子
	void analyse();//分析句子
	void printStack();//打印栈的内容

};

2. 알고리즘 분석 프로세스

(1) 사용자가 입력한 여러 문법 규칙을 식별하여 문법 생성

사용자가 입력한 문법 규칙은 문자열이기 때문에 프로그램은 단말, 비단말 등의 문자를 구분할 수 없기 때문에 사용자는 문법에 나타나는 모든 단말, 비단말을 입력한 후 규칙을 입력해야 합니다. 초기화는 FOLLOW 집합을 구성하고 나중에 스택을 분석할 때 시작 기호를 사용해야 하므로 사용자도 문법에서 시작 비단말 기호를 알려줘야 합니다. 위의 정보가 수집된 후 사용자는 문법 규칙을 다시 입력할 수 있는데, 빈 기호 문자열 ε은 입력하기 어려우므로 프로그램에서 '@'을 사용하여 대체합니다. 프로그램이 규칙을 인식하면 왼쪽과 오른쪽 부분으로 나누어 왼쪽 부분을 문자열로 저장하고 문자열에서 "->"를 삭제하면 오른쪽 부분을 인식하고 "|"를 인식한다. 분할로, 그리고 여러 오른쪽 부분이 vector<string>에 추가됩니다. 그리고 규칙의 개수를 알 수 없기 때문에 규칙의 입력은 '#'으로 끝난다.

 

(2) 직접 왼쪽 재귀를 감지하고 변환

직접 왼쪽 재귀가 있는 문법 규칙은 A::=Aa|b 형식이며 왼쪽 재귀는 새로운 비종단 기호 A'를 도입하여 제거할 수 있습니다. A' 소개, A::=Aa|b는 다음과 같이 동등하게 쓸 수 있습니다.

A::=bA'

A'::=aA'| ε

이렇게 하면 직접 왼쪽 재귀가 제거됩니다. 따라서 vector<grammar>production에 저장된 모든 문법 규칙을 순회하여 오른쪽 부분의 첫 번째 문자가 왼쪽 부분과 같으면 직접 왼쪽 재귀가 발견되었음을 의미합니다. 그런 다음 왼쪽 부분을 기준으로 '\''를 추가하여 새로운 비단말기를 형성하고(다른 비단말기와 중복되는 것을 방지하기 위해 이 방법을 사용함) 위의 규칙에 따라 문법을 다시 작성합니다. A의 오른쪽 부분은 Aa'와 같이 A'의 오른쪽 부분에 더해지고 A의 오른쪽 부분에서는 ε이 삭제됩니다. 그런 다음 b 모양인 A의 모든 오른쪽 부분을 bA'로 다시 작성하여 왼쪽 재귀 제거를 완료합니다.

(3) FIRST 세트와 FOLLOW 세트 풀기

<i> FIRST 세트 풀기

직접 왼쪽 재귀를 제거하여 문법의 FIRST 세트를 찾으십시오. 문법 의 각 문법 기호 X∈( V N V T) 에 대해 FIRST(X)를 구성할 때 FIRST 집합이 더 이상 확장되지 않을 때까지 다음 규칙을 계속 사용하십시오.

X∈ VT 이면 FIRST(X)={X}입니다.

· X ∈ VN 이면 X::=a α (a V T ) 또는 X::= ε 형식의 규칙이 있으며 FIRST(X)에 a 또는 ε를 추가합니다.

· 문법 G에 X::Y1Y2…Yk 형식의 규칙이 있다고 가정하고, Y1 V N 이면 모든 2<=i에 대해 FIRST(Y1)에서 FIRST(X)에 ε 이 아닌 기호를 모두 추가합니다. <=k , Y1 이면 Y2에 있는 첫 번째 기호 집합( ε 제외 )을 FIRST(X)에 추가하는 식으로 Yk-1 까지 추가 한 다음 Yk에 있는 첫 번째 기호 집합( ε 제외)을 추가합니다. 외부)도 FIRST(X)에 추가됩니다.

· Y1Y2...Yk의 각 비말단 기호가 빈 기호 문자열, 즉 Y1Y2...Yk *ε를 추론할 수 있는 경우 FIRST(X)에 ε을 추가합니다.

따라서 FIRST 집합을 푸는 과정은 두 부분으로 나뉘는데, 첫 번째 부분은 비단말기호에 대한 FIRST 집합을 찾는 것이고, 두 번째 부분은

터미널 기호의 FIRST 세트를 찾으십시오. 솔루션에서 규칙 순회 순서가 불확실하기 때문에 FIRST 집합의 무결성을 보장할 수 없으므로 FIRST 집합을 찾는 횟수를 2로 설정하여 각 비단말에 대해 완전한 FIRST 집합을 얻을 수 있도록 합니다. 상징. 또한, 빈 심볼 스트링 ε 에 대해서는 FIRST( ε ) = ε 이다 .

<ii> FOLLOW 세트 해결

문법의 각 비말단 기호 A에 대해 FOLLOW(A)를 구성하기 위해 각 FOLLOW 집합이 더 이상 확장되지 않을 때까지 다음 규칙을 반복적으로 적용할 수 있습니다.

문법의 시작 기호 S에 대해 # FOLLOW(S)라고 합니다.

· 문법에 A::= α B ββ≠ε 형식의 규칙이 있는 경우 FIRST( β )에서 ε 이 아닌 모든 기호를 FOLLOW(B)에 추가합니다.

· 문법에 A::= α B 또는 A:: A::= α B β 형식의 규칙이 있고 β * ε 인 경우 FOLLOW(A)의 모든 터미널은 FOLLOW(B에 속합니다. ), 여기서 α는 ε 일 수 있습니다 .

따라서 FIRST 집합을 푸는 과정과 동일하게 FOLLOW 집합을 푸는 과정은 위의 세 부분으로 나누어진다. 최종적으로 FOLLOW 집합의 완전성을 얻기 위해 FOLLOW 집합을 얻기 위한 전체 주기 수는 2로 설정됩니다.

(4) LL(1) 분석표 구축

FIRST 집합과 FOLLOW 집합을 계산한 후 문법 G의 각 규칙 A::= α 에 대해 테이블의 각 요소는 다음 알고리즘에 따라 결정될 수 있습니다(분석 테이블이 M이라고 가정).

· FIRST( α ) 의 각 터미널 a 에 대해 M[A,a]=“A-> α ”로 설정합니다.

· εϵ FIRST( α ) 이면 FOLLOW(A ) 에 속하는 각 기호 b(b는 단말 기호 또는 #)에 대해 M[A,b]=“A-> α ”로 설정합니다 .

위의 두 규칙으로 정의할 수 없는 M 의 모든 요소를 ​​오류로 설정합니다 .

따라서 행의 개수가 비단말기호의 개수이고 열의 개수가 단말기호의 개수에 1(+#)을 더한 값인 분석표 M은 위의 세 가지 규칙에 따라 구성할 수 있다.

(5) 사용자 입력 문장 분석

먼저 사용자가 입력한 문자열을 읽고 산술 문법의 경우 사용자가 입력한 문자열에 숫자가 포함되어 있으면 정수를 끝 기호 'i'로 바꿉니다. 예를 들어 사용자가 98+99+80을 입력하면 프로그램은 i+i+i로 처리합니다.

그런 다음 두 개의 스택(분석 스택과 나머지 문자열 스택)을 구축합니다. 두 개의 스택을 초기화하고 분석 스택은 #과 시작 기호 E를 스택에 각각 푸시하고 나머지 문자열 스택은 #을 먼저 푸시한 다음 처리된 사용자 입력 문자열을 역순으로 스택에 푸시합니다. 그런 다음 두 스택의 스택 탑을 읽고, LL(1) 분석 테이블을 확인하고, 문법 규칙이 발견되면 분석 스택의 스택 탑을 팝 아웃하고, 문법 규칙의 오른쪽 부분을 분석 스택으로 푸시합니다. 역순으로. 규칙이 없으면 오류를 보고하고 종료하여 입력 문장이 문법에 속하지 않음을 나타냅니다. 위의 과정을 반복하여 두 스택의 상위 요소가 같으면 제거하고 다음 주기를 수행하고 스택의 두 상위 요소가 모두 터미널 기호이고 동일하지 않으면 오류가 발생합니다. 신고하고 퇴장. 루프가 종료된 후 두 스택의 요소 수가 0이면 문법이 문장을 받아들일 수 있고 매칭이 성공한 것입니다.

추천

출처blog.csdn.net/weixin_45681165/article/details/121932862