Transcript مثال
#inlcude<iostream.h>
#inlcude<stdlib.h>
#inlcude<stdio.h>
int main(){
int state;
char ch;
state=1;
switch(state){
case 1: ch= getc(stdin);
if (ch==’a’)
state=2;
else {
cout<<"Failed";
exit(0);
}
break;
case 2: ch=getc(stdin);
if(ch=='\n')
cout<<"Accepted";
else
cout<<"Failed";
exit(0);
}
return(0);
}
1
a
2
#inlcude<iostream.h>
#inlcude<stdlib.h>
#inlcude<stdio.h>
int main(){
int state;
char ch;
state=1;
while(1){
switch(state){
case 1: ch = getc(stdin);
if (ch==’a’) state=1;
else if(ch == 'b') state=2;
else {
cout<<"Failed";
exit(0);
}
break;
case 2: ch=getc(stdin);
if(ch=='\n') cout<<"Accepted";
else
cout<<"Failed";
exit(0);
}
}
return(0);
}
a
b
1
2
#inlcude<iostream.h>
#inlcude<stdlib.h>
#inlcude<stdio.h>
int main(){
int state;
char ch;
state=1;
while(1){
[a-zA-Z0-9]
switch(state){
case 1: ch = getc(stdin);
if ((ch>=’a’ && 'z'>=ch )|| (ch>=’A’ && 'Z'>=ch) )
state=2;
else {
cout<<"Failed";
exit(0);
}
break;
[a-zA-Z]
[a-zA-Z
case 2:ch = getc(stdin);
1
if(ch=='\n'){
2
cout<<"Accepted";
exit(0);
}
else if ((ch>=’a’ && 'z'>=ch )|| (ch>=’A’ && 'Z'>=ch) || (ch>=’0’ && '9'>=ch))
state=2;
else {
cout<<"Failed";
exit(0);
}
exit(0);
break;
}
return(0);
}
[a-zA-Z0-9]
[0-9]
[a-zA-Z]
1
[1-9]
2
6
7
[0-9]
[0-9]
[ \n]
.
[1-9]
3
4
54
8
•
•
•
•
•
•
•
•
•
•
•
•
•
int fail(int start)
{
int nextstart;
switch(start){
case 1:nextstart=3; //
break;
case 3:nextstart=6; //
break;
case 6:nextstart=8; //
break;
}
return nextstart;
}
اعداد اعشاري
اعداد صحيح
فضاي خالي
int main{
int state,start,loc;
char ch;
FILE *fp;
fp=fopen("source.txt","r");
state=1;
start=1;
while(1)
switch(state){
case 1: loc=ftell(fp);
ch=getc(fp);
if((ch>='a' && ch<='z') || (ch>='A' && ch<='Z'))
state=2;
else{
start=fail(start);
state=start;
fseek(fp,loc,SEEK_SET);
}
break;
case 2:ch=getc(fp);
if((ch>='a' && ch<='z') || (ch>='A' && ch<='Z') || (ch>='0' && ch<='9'))
state=2;
else if (ch==' '|| ch=='\n'|| ch==EOF){
cout<<"ID ";
state=8;
}
else{
start=fail(start);
state=start;
fseek(fp,loc,SEEK_SET);
}
break;
case 3: loc=ftell(fp);
ch=getc(fp);
if(ch>='1' && ch<='9')
state=4;
else {
start=fail(start);
state=start;
fseek(fp,loc,SEEK_SET);
}
break;
اولویت ها
ممكن است بخشي از یك دنباله از كاراكترها با یك عبارت باقاعده و بخش دیگری با
عبارت باقاعده دیگري منطبق شود .به عنوان مثال دنباله 123.65و دو عبارت با قاعده ذیل
را در نظر مي گيریم.
*][1-9][0-9
*][1-9][0-9]*. [0-9][1-9
.
پذيرش طوالني ترين دنباله است
ممكن یك دنباله از كاراكترها (نه بخشي از یكك دنبالكه) بكا دو یكا دنكد عبكارت باقاعكده
منطبق شود به عنوان مثال رشته ifمي تواند توسط دو عبارت باقاعده ذیل توليد شود.
if
*][a-zA-Z][a-zA-Z0-9
عبارت با قاعده اي كه اولويت بيشتري دارد ،ابتدا مقايسه
مي شود.
int fail(int start){
int nextstart;
switch(start){
case 1:nextstart=6; //
break;
case 3:nextstart=8;
break;
case 6:nextstart=3; //
break;
}
return (nextstart);
}
اعداد صحيح
اعداد اعشاري
توليد خودكار تحليلگر لغوي
•
•
•
•
استفاده از ابزارها داراي مزايا و معايبي است كه عبارتند
از:
افزايش سرعت ايجاد تغييرات
كاهش زمان ساخت تحليلگر لغوي
افزايش محدوديتها
پياده سازي تحليلگر لغوي به وسيله توليدكننده
تحليلگر لغوي
FLexبرنامه به زبان
Flexكامپايل بوسيله
يا پاسكال Cبرنامه به زبان
Cكامپايل بوسيله كامپايلر
تحليلگر لغوي
•
•
•
•
c:\> flex pascal.l scanner.c
بعد از کامپايل و توليد فايل اجرايی
c:\> scanner.exe< p1
c:\> scanner.exe <p1> result
• c:\> scanner.exe
رشته هاي مورد نظر
نحوه بيان عبارات با قاعده
*r
r+
?r
}r{m,n
}X{2,5
r1r2
r1/r2
(If/
r1|r2
]مجموعه كاراكترها[
][afk
كاراكترهاي مورد نظر^[
][^afk
.
بجز سر خط
] كاراكتر مقصد -كاراكترمبدا[
][b-f
ساختار برنامه به زبان flex
تعاريف
%%
ترجمه ها
%%
توابع
•
•
•
•
•
مثال
digit
lower
upper
letter
var
ws
%%
ws
“if”
“else”
var
%%
[0-9]
[a-z]
[A-Z]
lower|upper
{letter}|({letter}|{digit})*
[ \n\t]+
{}
{printf(“I found ‘IF’ keyword”);}
{printf(“I found ‘ELSE’ keyword”);}
{printf(“I found variable “);}
.• اگر متن ذيل به عنوان ورودي به اين تحليلگر داده شود
• if temp else if id 34
.• خروجي به صورت ذيل خواهد بود
• I found ‘if’ keyword
• I found variable
• I found ‘ELSE’ keyword
• I found ‘if’ keyword
• I found variable
%option noyywrap
%{
int nchar,nline;
%}
%%
[\n] {nline++;}
.
{nchar++;}
}
%%
int main(void){
yylex();
printf("%d %d",nchar,nline+1);
return (0);
}
کلمات کليدی
روش اول
[a-zA-Z]([a-zA-Z0-9])*
"program"
"var"
"begin"
"end"
printf("ID ");
printf("PROGRAM ");
printf("VAR ");
printf("BEGIN ");
printf("END ");
void toupper(char k[])
{ int i;
for(i=0;i<=strlen(k);++i)
if(k[i]<='z' && k[i]>='a')
k[i]-=32;
}
int is_keyword(char id[]) {
char keyword[40][20]={"AND",
"ARRAY","BEGIN","CASE","CONST","DIV","DO","DOWNTO","ELSE","END","EXTERN
AL","EXTERN","FILE",
"FOR","FORWARD","FUNCTION","GOTO","IF","IN","LABEL","MOD","NIL","NOT","O
F","OR","OTHERWISE", "PROCEDURE",
"PROGRAM","RECORD","REPEAT","THEN","TO","TYPE","UNTIL","VAR","WHILE","W
ITH"};
int i;
for(i=0;i<40;i++)
if(strcmp(id,keyword[i])==0)
return i;
return -1;
}
%}
کلمات کليدی
روش دوم
[a-zA-Z]([a-zA-Z0-9])*
{toupper(yytext);
if (is_keyword(yytext)!=-1)
printf("keword=%s",yytext);
else
printf("ID ");
}
حساس به حالت
•
•
•
•
•
.
.
.
A [aA]
B [bB]
C [cC]
D [dD]
E [eE]
•
•
•
•
•
•
{A}{N}{D}
{A}{R}{R}{A}{Y}
{C}{A}{S}{E}
{C}{O}{N}{S}{T}
{D}{I}{V}
{D}{O}
return(AND);
return(ARRAY);
return(CASE);
return(CONST);
return(DIV);
return(DO);
حساس به حالت روش دوم
[a-zA-Z]([a-zA-Z0-9])* {toupper(yytext);
if (is_keyword(yytext)!=-1)
return (KW);
else
return(IDENTIFIER );
}
فاكتور گيري چپ
• براي يك غير پايانه ممكن است انتخابهاي مختلفي وجود
داشته باشد به طوريكه شروع يكسان داشته باشند .به عنوان
مثال به گرامرهاي ذيل دقت كنيد.
• A a B | aD
و يا •
• A cd B | cdg D
حذف بازگشتی دپ
• A 1 | 2 | 3 ... | n
• :شروع مشترك
• :iقسمت غير مشترك
• گرامر فوق را میتوان به صورت ذيل نشان داد كه همان
زبان را توليد میكند:
•
A R
• R 1 | 2 | 3 | ..
مثال
• A cd B | cdg D
• BbB | c
• DdD | e
• با توجه به AcdB|cdgDقسمت مشترك cdاست در نتيجه:
• = cd
• 1=gD
• 2=B
• گرامر را میتوان به صورت ذيل نشان داد.
• A cd R
• R B | g D
• BbB | c
• DdD | e
تحليلگر لغوي برنامه مبدا را به دنبالهاي از نشانهها تبديل •
كرده و به تحليلگر نحوي ارسال میكند .اگر اين دنباله
توسط گرامر زبان مبدا قابل توليد باشد ،برنامه مبدا از نظر
نحوي صحيح است در غير اين صورت داراي خطاي
نحوي است.
تجزیه
فرايند تجزيه نشان میدهد آيا دنبالهاي از نشانهها توسط •
گرامر قابل توليد است يا خير .روالي كه فرايند تجزيه را
انجام میدهد ،تجزيه كننده ناميده می شود.
انواع تجزيه كنندهها
• -تجزيه كنندههاي باال به پايين
• -تجزيه كنندههاي پايين به باال
ترتيب ساخته شدن درخت تجزیه
باال به پايينPreorder :
پايين به باالPostorder :
ترتيب ساختن درخت تجزیه در روش باال به پایين
ترتيب ساختن درخت تجزیه در روش پایين به باال
مثال
• E E + T | E - T | T
• T T * F | T / F | F
• F id
نحوه ساختن درخت تجزيه برای رشته • :
• Id+id+id
مثال
)first(
اگر دنبالهاي از پايانهها و غير پايانهها باشد ،مجموعه first
مربوط به پايانههايي را مشخص ميكند كه رشتههاي مشتق
شده از با آنها شروع ميشوند.
اگر بتواند را توليد كند ،نيز به ) first(اضافه ميشود.
مثال
A BCd
B bB | e |
C aC |
. مشتق شدهاند دقت كنيدBCd به رشتههايي كه از
BCdd
BCdbBCd
bBd
bd
BCdeCd
ed
BCdCd
aCd
ad
first(BCd)={d,b,e,a}
• با توجه به گرامر ذيل ) first(aAو ) first(aBرا محاسبه
كنيد.
• A→ a A|a B
• B→ b B| c
• با توجه به گرامر ،رشتههاي مشتق شده از aAفقط با aو
رشتههاي مشتق شده از aBفقط با aشروع ميشوند،
بنابراين:
} • first(aA) = {a
} • first(aB) = {a
قوانين محاسبه first
-1اگر پايانه باشد آنگاه ) first(برابر مجموعه } {است.
• مثال در گرامر فوق داريم:
}• first(a)={a
}• first(b)={b
•
-2اگر بتواند را توليد كند ،آنگاه به مجموعه
) first(اضافه میگردد.
-3اگر Xغير پايانه و XY1Y2Y3...Ynباشد و مجموعههاي
) first(Y1),first(Y2),...first(Ynشامل باشند يعني همه
Yiبتوانند تهي را توليد كنند .در نتيجه Xنيز میتواند را
توليد كند كه در اين صورت به مجموعه ) first(Xاضافه
میگردد.
مثالfirst(X):
• S AB
• A aA | bB | a |
• B bB | b |
• -4اگر Xغير پايانه و XY1Y2Y3...Ynباشد ،مجموعه
)) first(Y1به جز (به مجموعه ) first(Xاضافه میگردد.
زيرا ) first(Y1مجموعه پايانههايي هستند كه در شروع
رشتههايي كه توسط Y1توليد میشوند قرار دارند از
آنجاييكه Xبا Y1شروع میشود ،پس Xبا پايانههاي
) first(Y1شروع ميشوند ،در نتيجه ) first(Y1به )first(X
اضافه میگردد.
اگر Xغير پايانه و XY1Y2Y3...Ynباشد و در مجموعه
) first(Y1باشد ) Y1میتواند را توليد كند( در اين
صورت عالوه بر )) first(Y1به جز (مجموعه )first(Y2
)به جز (نيز به ) first(Xاضافه میگردد.
• به گرامر ذيل دقت كنيد
• A Bab
• Bc|d|
follow(A
•
• مجموعه )follow(Aپايانههايي را مشخص میكند كه در
اشتقاقهاي مختلف بالفاصله در سمت راست Aقرار
میگيرند اين مجموعه را با ) follow(Aنشان می دهيم.
X aXAad | a
A Ac | Af |
محاسبه
XaXAad
XaXAad aXAcad
XaXAad aXAcad aXAfcad
:با توجه به مراحل باال میتوان نتيجه گرفت كه
• follow(A)={a,c,f}
قوانين
اگر Sنماد شروع باشد $) $نشان دهنده آخر رشته ورودياست( به ) follow(Sاضافه میشود.
• -2براي هر قاعده توليدي به صورت ، MNتمام
پايانههاي موجود در ) ) first(به جز (به )follow(N
اضافه میشود.
A AXZ |
Z aZ | bZ | c |
X a
}follow(X)= {a,b,c
• براي هر قاعده اي به صورت MNو يا MN
در صورتيكه عضو ) first(باشد يعني بتواند رشته
تهي يا را توليد كند ،تمام پايانههاي مجموعه
) follow(Mبه ) follow(Nاضافه ميشود.
مثال
. را محاسبه كنيدfollow(B) • با توجه به گرامر ذيل
• AAXb
• Xd|dB|eBE
• Ea|
• Bb
:جواب
follow(B)={a,b}
تجزيه كننده باال به پايين
انواع تجزيه کننده های باال به پايين:
تجزيه کننده های بازگشتی -تجزيه کننده غير بازگشتی
تجزیه کننده های بازگشتی
در اين تجزيه کننده يک متغير پيشنگر )(lookaheadهمواره به نماد جاری رشته ورودی اشاره می کند
تجزيه کننده سعی می کند نمادی که پيشنگر به آن اشاره میکند را توليد کند.
هرگاه نماد مورد نظر پيشنگر توليد شد ،پيشنگر به نمادبعدی اشاره می کند.
-برای هر غير پايانه يک تابع ايجاد می شود
مثال
A→ aR
R→ A|B
B→ bB|c
تابع کمکی :matchهرگاه نماد مورد انتظار گرامر
) (symbolبا نماد جاری رشته ورودی )(lookahead
يکسان شد .يعنی يکی از نمادهای رشته جاری توليد شده
است و پيشنگر بايد به جلو حرکت کند .وگرنه خطا است.
{)void match(char symbol
)if ( lookahead== symbol
;)(lookahead=getche
{else
;"cout<<" error
;)exit(0
}
}
A→ aR
void A(){
match('a');
R(); }
R→ A|B
B→ bB|c
first(A)={a}
first(B)={b,c}
void R(){
if(lookahead=='a') A();
else if (lookahead=='b' || lookahead=='c')
B();
else{ cout<"error";exit(0);}
}
B→ bB|c
void B(){
if(lookahead=='b'){
match('b');
B();
}
else if(lookahead=='c'){
match('c');
cout<<"Accepted";
exit(0);
}
}
int main(){
lookahead= getche();
A();
return 0;
}
مشکالت روش پيشگوی غير بازگشتی
• برخورد first/first
فرض کنيد قانون زير بخشی از گرامر است.
B→ bB|bc
B→ α1|α2
First(α1) ∩first(α2) ≠φ
راه حل :فاکتور گيری چپ:
B→ bB|bc
B→ bR
R→ B|c
first/follow برخورد
A→ Bed
B → e|a|
char lookahead;
void A(){
B();
match('e');
match('d');
}
void B(){
if (lookahead =='e'){
match('e');
else if (lookahead =='a')
match('a');
else ;
return ;
}
int main(){
lookahead=getche();
A();
return 0;
گرامر
A→ Bed
B → e|a|
eed وed عملکرد تجزيه کننده برای دو رشته
.چيست
• برخورد first/follow
•
•
•
•
در قاعده توليدي به صورت A|شرايط ذيل برقرار
باشد.
-1بتواند را توليد كند.
-2حداقل يك نماد وجود دارد كه هم میتواند در شروع
و هم بعد از Aباشد .يا به عبارت ديگر رابطه ذيل برقرار
باشد.
first() follow(A)
A→ Bed
B→ e|a|
، باشد= و=e اگر
first(e)={e}
follow(B)={e}
:در نتيجه
first(e) follow(B)={e}
تجزيه كننده پيشگوي غير بازگشتي
AB|C
B bB | f
C cC | e
expr term rest
rest + expr | - expr |
term id
تمام غير پايانههاfollow تمام سمت راستهاي قواعد توليد وfirst ابتدا
.را محاسبه میكنيم
first(term rest)=first(term)={id}
first(+expr)=first(+)={+}
first(-expr)=first(-)={-}
first()={}
first(id)={id}
follow(expr)={$}
follow(term)={+,-,$}
follow(rest)={$}
ساختار تجزیه کننده پيشگوی غير بازگشتی
مراحل تجزیه id+id-id
گرامرهای )LL(1
اگر در ساخت درخت تجزيه فقط با رويت يك نشانه بعدي از
رشته ورودي در پيمايش چپ به راست ،بتوان غير پايانه
بعدي را براي گسترش تشخيص داد در اين صورت گرامر
مستقل از متن را ) LL(1ميناميم.
مثال:
• A aB | aad
• B bB | c
اين گرامر ) LL(1نيست.
•
•
•
•
•
گرامرهاي داراي بازگشتي چپ ) LL(1نيستند. گرامرهاي مبهم ) LL(1نيستند. اگر جدول تجزيه غيربازگشتي پيشگو داراي خانهاي با بيشاز يك وارده باشد ،گرامر ) LL(1نيست و به عكس اگر جدول
تجزيه پيشگوی غيربازگشتي داراي خانهاي با بيش از يك وارده
نباشد ،گرامر ) LL(1است .در نتيجه توليد جدول تجزيه يكي از
مهمترين روشهاي تست ) LL(1بودن گرامر است.
گرامرهای دارای برخورد first/firstاز نوع ) LL(1نيستند.گرامرهای دارای برخورد first/followاز نوع ) LL(1نيستند.
• اگر در گرامر ،قاعده توليدي به صورت A|وجود داشته
باشد به طوريكه و هر دو رشته تهي را توليد كنند ،گرامر
) LL(1نيست.
• گرامر ذيل را در نظر بگيريد.
• ACB |
• BbB |
• C cC |
• A aCbAB | d
• B eA|
• C c
استفاده از قوانين توليد خطا :اگر خطاهايي كه در برنامه رخ میدهد رابتوان پيش بيني كرد میتوان قواعد توليدي به گرامر اضافه كرد تا اين خطا
را نيز شامل گردد .در اين صورت تجزيه كننده با كاهش چنين گرامري
خطا را كشف میكند و میتواند به تجزيه نيز ادامه دهد.
گرامر ذيل نشان میدهد كه بعد از هر دستور سمي كالن قرار دارد.
; stmtstmt ; stmt_list | stmt
با توجه به آمار ،يكي از خطاهايي كه معموال در برنامه رخ میدهد عدم درج
سمي كالن است در نتيجه قاعده توليدي به صورت ذيل نيز به گرامر اضافه
میگردد.
stmt_errorstmt stmt_list | stmt
در نتيجه اگر برنامه نويس سمي كالن را درج نكند دستورات به جاي stmtبا
stmt_errorكاهش میيابد بنابراين تجزيه كننده متوقف نمیگردد بلكه پيغام
مناسب را صادر و تجزيه ادامه میيابد.
مهمترین روشهاي تصحيح خطا عبارتند از:
• -1تصحيح حالت اضطراري :در اين روش تجزيه كننده هنگام كشف يك
خطا ،نمادهاي ورودي را تا رسيدن به يك عالمت هماهنگ كننده كه به
وسيله طراح كامپايلر معين شده است ناديده میگيرد .به عنوان مثال عالمت
سمي كالن در زبان Cيك عالمت هماهنگ كننده است.
مدیریت خطا
• -1اگر پايانهاي مانند aباالي پشته باشد كه با نماد جاري يكسان
نباشد ،خطا رخ ميدهد ،به منظور پوشش خطا ،نماد aرا به
ورودي اضافه میكنيم.
• SbcA
• AaA|c
رشته bdcc
• نمادهاي ) first(Aرا به عنوان مجموعه هماهنگ كننده A
در نظر میگيريم .بنابراين اگر يكي از نمادهاي )first(A
در ورودي ظاهر شود تجزيه بر اساس Aادامه میيابد.
• نمادهاي ) follow(Aرا به عنوان نمادهاي هماهنگ كننده
Aدر نظر میگيريم.
تجزيه كننده پايين به باال
تجزيه کننده های عملگر اولويت -تجزيه کننده های LR
:مثالی از نحوه عملکرد تجزيه کننده پايين به باال
expr expr + term | expr - term | term
term 1|2|3|4
4+1-2
term +1-2
expr + 1 -2
expr + term -2
expr - 2
expr - term
expr
•
•
•
•
•
•
•
1+2-+3
term+ 2-+3
expr + 2 -+ 3
expr + term -+3
expr -+ 3
expr -+ term
expr -+ expr
دستگيره
دستگيره ،دنبالهاي است كه منطبق بر سمت راست يك •
قاعده توليد بوده و كاهش آن به سمت چپ قاعده توليد يك
مرحله از مراحل معكوس سمت راستترين اشتقاق است.
• دستگيرهها را در كاهش رشته bccdefبا توجه به گرامر
ذيل مشخص كنيد.
SbBCf
BBcd|c
Ce
ابتدا رشته bccdefرا بوسيله سمت راست ترين اشتقاق توليد
میكنيم.
S
bBCf
bBef
bBcdef
bccdef
عملکرد تجزیه کننده های LR
يافتن دستگيره -کاهش دستگيره
ساختار تجزیه کننده های LR
مثال
گرامر ذيل را در نظر میگيريم.
E→E+T | T
T→id
گرامر به شکل زير تغييرمی کند.
1- S → E
2- E→E+T
3- E→T
4- T→id
مراحل تجزیه رشته id+id
مهمترين روشهاي موجود عبارتند از:
: LR(0) -1ساده ترين و ضعيفترين روش است.
SLR(1) LR) -2ساده( :اين روش از ) LR(0قويتر است.
LR(1) -3يا LRمتعارف :قويترين روش ساخت جدول
تجزيه LRاست.
:LALR(1) -4از روش ) SLR(1قويتر و از ) CLR(1ضعيفتر
است.
روش )LR(0
عنصر): LR(0
يك قاعده توليد است كه نقطه اي در سمت راست آن قرار دارد(
مانند .) S→α.β :مكان نقطه در عنصر LRميزان پيشرفت ،در
كاهش غير پايانه سمت چپ(مانند )Sرا نشان میدهد.
S→.XYZ
S→X.YZ
S→XY.Z
S→XYZ.
آخر سمت راست قاعده توليد
در
عنصر كاهش ي :عنصري كه نقطه
•
عنصر نشان دهنده يافتن يك دستگيره است .به
قر ار دارد .اين نوع
عنوان مثال عنصر S→XYZ.نشان میدهد كه XYZيك دستگيره
است كه میتوان آن را به Sكاهش داد.
ي كه كاهش ي نباشد .به عنوان مثال S→X.YZ
عنصر انتقالي :عنصر
•
در
عنصر انتقالي نشان دهنده فرضيهاي
هر
عنصر انتقالي است .
يك
مورد دستگيره بعدي است به عنوان مثال S→X.YZنشان میدهد
كه دستگيره ممكن است از Yبه دست آيد.
ساخت ماشين متناهی
E→E+T|T
T→id
E→E+T
E→T
T→id
S→E
E→E+T
E→T
T→id
S→.E
S→.E
E→.E+T
E→.T
S→.E
E→.E+T
E→.T
T→.id
E→E+T|T
T→id
برخورد انتقال /کاهش
S→E
E→E+T
E→T
T→id
] T→id [ E
کاهش/ برخورد کاهش
S→E
E→E+T
E→T
E→X
T→id
X→ id