詞法分析
1、 待分析的簡單語言的詞法
(1) 關鍵字:(所有關鍵字都是小寫。)
begin if then while do end
(2) 運算符和界符:
:= + – * / < <= <> > >= = ; ( ) #
(3) 其他單詞是標識符(ID)和整型常數(NUM),通過以下正規式定義:
ID=letter(letter| digit)*
NUM=digit digit *
(4) 空格由空白、製表符和換行符組成。空格一般用來分隔ID、NUM,運算符、界符和關鍵字,詞法分析階段通常被忽略。
2、 各種單詞符號對應的種別碼
單詞符號 |
種別碼 |
單詞符號 |
種別碼 |
begin |
1 |
: |
17 |
if |
2 |
:= |
18 |
then |
3 |
> |
20 |
while |
4 |
<> |
21 |
do |
5 |
<= |
22 |
end |
6 |
< |
23 |
letter(letter| digit)* |
10 |
>= |
24 |
digit digit * |
11 |
= |
25 |
* |
13 |
; |
26 |
/ |
14 |
( |
27 |
+ |
15 |
) |
28 |
- |
16 |
# |
0 |
依照狀態轉換圖,進行匹配:
對單一字符,直接比較;
對關鍵字,進行循環匹配(0-1-2);
對數字,進行循環匹配(0-3-4)。
4、詞法分析程序的功能
輸入:所給文法的源程序字符串。
輸出:二元組(syn,token/sum)構成的序列。
其中:syn爲單詞種別碼;
token爲存放的單詞自身字符串;
sum爲整型常數。
錯誤:返回row行數
5、設計思路
設計scaner,查詢當前所指字符,並與表格內字符進行比較匹配:
匹配成功,返回相應代碼號;
匹配失敗,則彈出錯誤信息(用row記錄當前處理的行數)。
6、源程序代碼:
#include<stdio.h>
#include<string.h>
#include<iostream.h>
char prog[80],token[8];
char ch;
int syn,p,m=0,n,row,sum=0;
char *rwtab[6]={"begin","if","then","while","do","end"};
//匹配字符
void scaner()
{
for(n=0;n<8;n++) token[n]=NULL;
ch=prog[p++];
while(ch==' ')
{
ch=prog[p];
p++;
}
if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z'))
{
m=0;
while((ch>='0'&&ch<='9')||(ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z'))
{
token[m++]=ch;
ch=prog[p++];
}
token[m++]='\0';
p--;
syn=10;
for(n=0;n<6;n++)
if(strcmp(token,rwtab[n])==0)
{
syn=n+1;
break;
}
}
else if((ch>='0'&&ch<='9'))
{
{
sum=0;
while((ch>='0'&&ch<='9'))
{
sum=sum*10+ch-'0';
ch=prog[p++];
}
}
p--;
syn=11;
if(sum>32767)
syn=-1;
}
else switch(ch)
{
case'<':m=0;token[m++]=ch;
ch=prog[p++];
if(ch=='>')
{
syn=21;
token[m++]=ch;
}
else if(ch=='=')
{
syn=22;
token[m++]=ch;
}
else
{
syn=23;
p--;
}
break;
case'>':m=0;token[m++]=ch;
ch=prog[p++];
if(ch=='=')
{
syn=24;
token[m++]=ch;
}
else
{
syn=20;
p--;
}
break;
case':':m=0;token[m++]=ch;
ch=prog[p++];
if(ch=='=')
{
syn=18;
token[m++]=ch;
}
else
{
syn=17;
p--;
} break;
case'*':syn=13;token[0]=ch;break;
case'/':syn=14;token[0]=ch;break;
case'+':syn=15;token[0]=ch;break;
case'-':syn=16;token[0]=ch;break;
case'=':syn=25;token[0]=ch;break;
case';':syn=26;token[0]=ch;break;
case'(':syn=27;token[0]=ch;break;
case')':syn=28;token[0]=ch;break;
case'#':syn=0;token[0]=ch;break;
case'\n':syn=-2;break;
default: syn=-1;break;
}
}
//主程序讀入 循環
void main()
{
p=0;
row=1;
cout<<"Please input string:"<<endl;
do
{
cin.get(ch);
prog[p++]=ch;
}
while(ch!='#');
p=0;
do
{
scaner();
switch(syn)
{
case 11: cout<<"("<<syn<<","<<sum<<")"<<endl; break;
case -1: cout<<"Error in row "<<row<<"!"<<endl; break;
case -2: row=row++;break;
default: cout<<"("<<syn<<","<<token<<")"<<endl;break;
}
}
while (syn!=0);
}
7、 結果驗證
begin y:=11 if y<13 then y:1+2*3+y/2; end#
源程序(包括上式未有的while、do以及判斷錯誤語句):
begin
x<&
while
h<0
do
b>9
k>9-x
end
#
$ 字符不在表內,所以匹配錯誤,返回錯誤行數。
語法分析
1、分析文法
- 語法表達式
G(E):E à E +T | T
T à T* F | F
F à i | (E)
- 展開表達式
(1) E à E +T
(2) E à T
(3) T à T* F
(4) T à F
(5) F à i
(6) F à (E)
- 消除左遞歸:
G’(E):E->TE'
E'->+TE'|ε
T->FT'
T'->*FT'|ε
F->(E)|i
2、構造LL(1)分析表
非終結符 |
輸入符號 |
|||||
i |
+ |
* |
( |
) |
# |
|
E |
E->TE' |
|
|
E->TE' |
synch |
synch |
E’ |
|
E'->+TE' |
|
|
E'->ε |
E'->ε |
T |
T->FT' |
synch |
|
T->FT' |
synch |
synch |
T’ |
|
T'->ε |
T'->*FT' |
|
T'->ε |
T'->ε |
F |
F->i |
synch |
synch |
F->(E) |
synch |
synch |
- 構造LR分析表
狀態 |
action |
goto |
|||||||
id |
+ |
* |
( |
) |
# |
E |
T |
F |
|
0 |
S5 |
|
|
S4 |
|
|
1 |
2 |
3 |
1 |
|
S6 |
|
|
|
acc |
|
|
|
2 |
|
R2 |
S7 |
|
R2 |
R2 |
|
|
|
3 |
|
R4 |
R4 |
|
R4 |
R4 |
|
|
|
4 |
S5 |
|
|
S4 |
|
|
8 |
2 |
3 |
5 |
|
R6 |
R6 |
|
R6 |
R6 |
|
|
|
6 |
S5 |
|
|
S4 |
|
|
|
9 |
3 |
7 |
S5 |
|
|
S4 |
|
|
|
|
10 |
8 |
|
S6 |
|
S11 |
|
|
|
|
|
9 |
|
R1 |
S7 |
|
R1 |
R1 |
|
|
|
10 |
|
R3 |
R3 |
|
R3 |
R3 |
|
|
|
11 |
|
R5 |
R5 |
|
R5 |
R5 |
|
|
|
- LL(1)狀態切換
3、設計程序
LL(1)——————————————————————————————————
#include <iostream.h>
char inputstream[50];
int temp=0;
int right;
//狀態轉換函數
void e();
void e1();
void t();
void t1();
void f();
//主程序
void main()
{
right=1;
cout<<"請輸入您要分析的字符串以#結束(^爲空字符):"<<endl;
cin>>inputstream;
e();
if((inputstream[temp]=='#')&&right)
cout<<"分析成功"<<endl;
else
cout<<"分析失敗"<<endl;
}
void e()
{
cout<<"E->TE'"<<endl;
t();
e1();
}
void e1()
{
if(inputstream[temp]=='+')
{
cout<<"E'->+TE'"<<endl;
temp++;
t();
e1();
}
else
if (inputstream[temp]!='#'||inputstream[temp]!=')')
{
cout<<"T'->^"<<endl;
return ;
}
else
right=0;
}
void t()
{
cout<<"T->FT'"<<endl;
f();
t1();
}
void t1()
{
if(inputstream[temp]=='*')
{
cout<<"T'->*FT'"<<endl;
temp++;
f();
t1();
}
else
if (inputstream[temp]!='#'&&inputstream[temp]!=')'&&inputstream[temp]!='+')
{
cout<<"T'->^"<<endl;
right=0;
}
}
void f()
{
if(inputstream[temp]=='i')
{
cout<<"F->i"<<endl;
temp++;
}
else
if(inputstream[temp]=='(')
{
cout<<"F->(E)"<<endl;
temp++;
e();
if(inputstream[temp]==')')
{
cout<<"F->(E)"<<endl;
temp++;
}
else
right=0;
}
else right =0;
}
LR——————————————————————
#include<iostream>
#include<stack>
using namespace std;
char inputstream[50];
//棧區
//符號和狀態
stack<char> symbol;
stack<int> state;
//分析表內容
//符號表
char sym[12][6]={
{'s','e','e','s','e','e'},
{'e','s','e','e','e','a'},
{'r','r','s','r','r','r'},
{'r','r','r','r','r','r'},
{'s','e','e','s','e','e'},
{'r','r','r','r','r','r'},
{'s','e','e','s','e','e'},
{'s','e','e','s','e','e'},
{'e','s','e','e','s','e'},
{'r','r','s','r','r','r'},
{'r','r','r','r','r','r'},
{'r','r','r','r','r','r'}
};
//數字表
char snum[12][6]={
{5,1,1,4,2,1},
{3,6,5,3,2,0},
{2,2,7,2,2,2},
{4,4,4,4,4,4},
{5,1,1,4,2,1},
{6,6,6,6,6,6},
{5,1,1,4,2,1},
{5,1,1,4,2,1},
{3,6,5,3,11,4},
{1,1,7,1,1,1},
{3,3,3,3,3,3},
{5,5,5,5,5,5}
};
//goto表
int go2[12][3]={
{1,2,3},
{0,0,0},
{0,0,0},
{0,0,0},
{8,2,3},
{0,0,0},
{0,9,3},
{0,0,10},
{0,0,0},
{0,0,0},
{0,0,0},
{0,0,0}
};
//action函數[i,a]
void action(int i,char *&a,char &how,int &num,char &A,int &b)
{
int j;//指向action表列號
switch(*a)
{
case 'i':
j=0;break;
case '+':
j=1;break;
case '*':
j=2;break;
case '(':
j=3;break;
case ')':
j=4;break;
case '#':
j=5;break;
default:
j=-1;break;
}
if(j!=-1)
{
how=sym[i][j];
num=snum[i][j];
if(how=='r')
{
switch(num)
{
case 1:
A='E',b=3;
cout<<"按E->E+T規約"<<endl;
break;
case 2:
A='E',b=1;
cout<<"按E->T規約"<<endl;
break;
case 3:
A='T',b=3;
cout<<"按T->T*F規約"<<endl;
break;
case 4:
A='T',b=1;
cout<<"按T->F規約"<<endl;
break;
case 5:
A='F',b=3;
cout<<"按F->(E)規約"<<endl;
break;
case 6:
A='F',b=1;
cout<<"按F->id規約"<<endl;
break;
default:
break;
}
}
}
}
//goto[t,A]
int go(int t,char A)
{
switch(A)
{
case 'E':
return go2[t][0];break;
case 'T':
return go2[t][1];break;
case 'F':
return go2[t][2];break;
}
}
//error處理函數
void error(int i,int j,char *&a)
{
cout<<"error"<<endl;
switch(j)
{
case 1://期望輸入id或左括號,但是碰到+,*,或$,就假設已經輸入id了,轉到狀態5
state.push(5);
symbol.push('i');//必須有這個,如果假設輸入id的話,符號棧裏必須有....
cout<<"缺少運算對象id"<<endl;
break;
case 2://從輸入中刪除右括號
a++;
cout<<"不配對的右括號"<<endl;
break;
case 3://期望碰到+,但是輸入id或左括號,假設已經輸入算符+,轉到狀態6
state.push(6);
symbol.push('+');
cout<<"缺少運算符"<<endl;
break;
case 4://缺少右括號,假設已經輸入右括號,轉到狀態11
state.push(11);
symbol.push(')');
cout<<"缺少右括號"<<endl;
break;
case 5:
a++;
cout<<"*號無效,應該輸入+號!"<<endl;
case 6:
a++;
}
}
int main()
{
int s;
char *a;//當前操作符
char how;
int num;
int b;
char A;
while(1)
{
cin>>inputstream;
a=inputstream;
state.push(0);//0狀態入棧
while(*a!='\0')
{
b=0;num=0;how='\0';A='\0';
s=state.top();
action(s,a,how,num,A,b);
if(how=='s')//移進
{
cout<<"移進"<<endl;
symbol.push(*a);
state.push(num);
a++;
}
else if(how=='r')//規約
{
for(int i=0;i<b;i++)
{
if(!state.empty())
state.pop();
if(!symbol.empty())
symbol.pop();
}
int t=state.top();
symbol.push(A);
state.push(go(t,A));
}
else if(how=='a')//接受
break;
else
{
error(s,num,a);//錯誤處理
}
}
cout<<"成功接受"<<endl;
}
return 0;
}
4、總結
學完語法分析這章後,對LL、LR、SLR、LR(1)、LALR還有着不少疑問。
LL是常用的自上而下的語法分析,方法比較簡單容易理解,從非終結符到終結符,樹節點到葉節點;LR文法則是自下而上,將終結符一步步規約縮減,從葉節點直到規約成樹根,LR(0)-SLR、LR(1)是對是否規約衝突選擇的不同方法,LALR瞭解較少,爲了處理LR(1)的同心集合的衝突設置的文法。
參考了部分教材和代碼後,簡單編寫(理解並改動部分)了詞法分析和語法分析器(也是老師作業- -)。
詞法分析器作用無非是剔除無關字符、將特殊字符分類放到表中作爲後續(如語法分析的操作符+/-/*等)、報錯(主要是鍵入錯誤)。
語法分析器顧名思義,就是判斷句子說法是否正確,例如:+號左右是否都是數字或未知數,遇到不合理的‘=+*’就要相應報錯;進一步的語法分析,除了語句還有聲明、函數名、變量聲明....