![編譯原理教程04語義分析及中間代碼生成_第1頁](http://file4.renrendoc.com/view/aae2ed594b06b840051aee003090b083/aae2ed594b06b840051aee003090b0831.gif)
![編譯原理教程04語義分析及中間代碼生成_第2頁](http://file4.renrendoc.com/view/aae2ed594b06b840051aee003090b083/aae2ed594b06b840051aee003090b0832.gif)
![編譯原理教程04語義分析及中間代碼生成_第3頁](http://file4.renrendoc.com/view/aae2ed594b06b840051aee003090b083/aae2ed594b06b840051aee003090b0833.gif)
![編譯原理教程04語義分析及中間代碼生成_第4頁](http://file4.renrendoc.com/view/aae2ed594b06b840051aee003090b083/aae2ed594b06b840051aee003090b0834.gif)
![編譯原理教程04語義分析及中間代碼生成_第5頁](http://file4.renrendoc.com/view/aae2ed594b06b840051aee003090b083/aae2ed594b06b840051aee003090b0835.gif)
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第4章語義分析和中間代碼生成4.1概述4.2屬性文法4.3幾種常見的中間語言4.4表達式及賦值語句的翻譯4.5控制語句的翻譯4.6數(shù)組元素的翻譯4.7過程或函數(shù)調用語句的翻譯4.8說明語句的翻譯4.9遞歸下降語法制導翻譯方法簡介習題四4.1概述語義分析的概念語法制導翻譯方法
4.1.1語義分析的概念
一個源程序經過詞法分析、語法分析之后,表明該源程序在書寫上是正確的,并且符合程序語言所規(guī)定的語法。
但是語法分析并未對程序內部的邏輯含義加以分析,因此編譯程序接下來的工作是語義分析,即審查每個語法成分的靜態(tài)語義。
如果靜態(tài)語義正確,則生成與該語言成分等效的中間代碼,或者直接生成目標代碼。
直接生成機器語言或匯編語言形式的目標代碼的優(yōu)點是編譯時間短且無需中間代碼到目標代碼的翻譯,
而中間代碼的優(yōu)點是使編譯結構在邏輯上更為簡單明確,特別是使目標代碼的優(yōu)化比較容易實現(xiàn)。靜態(tài)語義檢查是在編譯時完成的,它涉及以下幾個方面:
(1)類型檢查,如參與運算的操作數(shù)其類型應相容。
(2)控制流檢查,用以保證控制語句有合法的轉向點。如C語言中不允許goto語句轉入case語句流;break語句需尋找包含它的最小switch、while或for語句方可找到轉向點,否則出錯。(3)一致性檢查,如在相同作用域中標識符只能說明一次,case語句的標號不能相同等。
語義分析階段只產生中間代碼而不生成目標代碼的方法使編譯程序的開發(fā)變得較為容易,
但語義分析不像詞法分析和語法分析那樣可以分別用正規(guī)文法和上下文無關文法描述。
由于語義是上下文有關的,因此語義的形式化描述是非常困難的,
目前較為常見的是用屬性文法作為描述程序語言語義的工具,并采用語法制導翻譯的方法完成對語法成分的翻譯工作。4.1.2語法制導翻譯方法語法制導翻譯的方法就是為每個產生式配上一個翻譯子程序(稱語義動作或語義子程序),并在語法分析的同時執(zhí)行這些子程序。在語法分析過程中,當一個產生式獲得匹配(對于自上而下分析)或用于歸約(對于自下而上分析)時,此產生式相應的語義子程序就進入工作,完成既定的翻譯任務。語法制導翻譯分為自下而上語法制導翻譯和自上而下語法制導翻譯,我們重點介紹自下而上語法制導翻譯。假定有一個自下而上的LR分析器,我們可以把這個LR分析器的能力加以擴大,使它能在用某個產生式進行歸約的同時調用相應的語義子程序進行有關的翻譯工作。
每個產生式的語義子程序執(zhí)行之后,某些結果(語義信息)必須作為此產生式的左部符號的語義值暫時保存下來,以便以后語義子程序引用這些信息。此外,原LR分析器的分析棧也加以擴充,以便能夠存放與文法符號相對應的語義值。這樣,分析??梢源娣湃愋畔ⅲ悍治鰻顟B(tài)、文法符號及文法符號對應的語義值。擴充后的分析棧如圖4–1所示。圖4–1擴充后的LR分析棧
作為一個例子,我們考慮下面的文法及語義動作所執(zhí)行的程序: 產生式 語義動作
(0)S'→E printval[TOP]
(1)E→E(1)+E(2) val[TOP]=val[TOP]+val[TOP+2]
(2)E→E(1)*E(2) val[TOP]=val[TOP]*val[TOP+2]
(3)E→(E(1)) val[TOP]=val[TOP+1]
(4)E→i val[TOP]=lexval(注:lexval為i的整型內部值)這個文法的LR分析表見表3.20。我們擴充分析棧工作的總控程序功能,使其在完成語法分析的同時也能完成語義分析工作(這時的語法分析棧已成為語義分析棧);即在用某一個規(guī)則進行歸約之后,調用相應的語義子程序完成與所用產生式相應的語義動作,并將每次工作后的語義值保存在擴充后的“語義值”棧中。圖4–2表示算術表達式7+9*5#的語法樹及各結點值,而表4.1則給出了根據(jù)表3.20用LR語法制導翻譯方法得到的該表達式的語義分析和計值過程。圖4–2語法制導翻譯計算表達式7+9*5#的語法樹4.2屬性文法文法的屬性屬性文法4.2.1文法的屬性
屬性是指與文法符號的類型和值等有關的一些信息,在編譯中用屬性描述處理對象的特征。
隨著編譯的進展,對語法分析產生的語法樹進行語義分析,且分析的結果用中間代碼描述出來。
根據(jù)語義處理的需要,在用產生式A→αXβ進行歸約或推導時,應能準確而恰當?shù)乇磉_文法符號X在歸約或推導時的不同特征。
例如,判斷變量X的類型是否匹配,要用X的數(shù)據(jù)類型來描述;
判斷變量X是否存在,要用X的存儲位置來描述;
而對X的運算,則要用X的值來描述。
因此,語義分析階段引入X的屬性,如X.type、X.place、X.val等來分別描述變量X的類型、存儲位置以及值等不同的特征。文法符號的屬性可分為繼承屬性與綜合屬性兩類。
繼承屬性用于“自上而下”傳遞信息。繼承屬性由相應語法樹中結點的父結點屬性計算得到,即沿語法樹向下傳遞,由根結點到分枝(子)結點,它反映了對上下文依賴的特性。繼承屬性可以很方便地用來表示程序語言上下文的結構關系。
綜合屬性用于“自下而上”傳遞信息。綜合屬性由相應語法分析樹中結點的分枝結點(即子結點)屬性計算得到,其傳遞方向與繼承屬性相反,即沿語法分析樹向上傳遞,從分枝結點到根結點。4.2.2屬性文法
屬性文法是一種適用于定義語義的特殊文法,即在語言的文法中增加了屬性的文法,它將文法符號的語義以“屬性”的形式附加到各個文法的符號上(如上述與變量X相關聯(lián)的屬性X.type、X.place和X.val等),
再根據(jù)產生式所包含的含義,給出每個文法符號屬性的求值規(guī)則,從而形成一種帶有語義屬性的上下文無關文法,即屬性文法。屬性文法也是一種翻譯文法,屬性有助于更詳細地指定文法中的代碼生成動作。例如,簡單算術表達式求值的屬性文法如下: 產生式 語義規(guī)則
(1)?S→E print(E.val) (2)?E→E(1)+T
E.val=E(1).val+T.val (3)?E→T E.val=T.val (4)?T→T(1)*F T.val=T(1).val*F.val (5)?T→T(1) T.val=T(1).val (6)?F→(E) F.val=E.val (7)?F→i F.val=i.lexval上面的一組產生式中,每一個非終結符都有一個屬性val來表示整型值,如E.val表示E的整型值,而i.lexval則表示i的整型內部值。
與產生式關聯(lián)的每一個語義規(guī)則的左部符號E、T、F等的屬性值的計算由其各自相應的右部符號決定,這種屬性也稱為綜合屬性。
與產生式S→E關聯(lián)的語義規(guī)則是一個函數(shù)print(E.val),其功能是打印E產生式的值。S在語義規(guī)則中沒有出現(xiàn),可以理解為其屬性是一個虛屬性。4.3幾種常見的中間語言
抽象語法樹逆波蘭表示法三地址代碼4.3.3三地址代碼
1.三地址代碼的形式
2.三地址語句的種類
3.三地址代碼的具體實現(xiàn)1.三地址代碼的形式
三地址代碼語句的一般形式為x=yopz其中,x、y和z為名字、常量或編譯時產生的臨時變量;
op為運算符,如定點運算符、浮點運算符和邏輯運算符等。
三地址代碼的每條語句通常包含三個地址,兩個用來存放運算對象,一個用來存放運算結果。由于三地址語句只含有一個運算符,因此多個運算符組成的表達式必須用三地址語句序列來表示,如表達式x+y*z的三地址代碼為:t1=y*z;t2=x+t1
3.三地址代碼的具體實現(xiàn)三地址代碼是中間代碼的一種抽象形式。在編譯程序中,三地址代碼語言的具體實現(xiàn)通常有三種表示方法:
四元式、三元式和間接三元式。
1)四元式四元式是具有四個域的記錄結構,這四個域為(op,arg1,arg2,result)其中,op為運算符;arg1、arg2及result為指針,它們可指向有關名字在符號表中的登記項或一臨時變量(也可空缺)。常用的三地址語句與相應的四元式對應如下:x=yopz 對應(op,y,z,x)x=?y 對應(uminus,y,_,x)x=y 對應(=,y,_,x)parx1 對應(par,x1,_,_)callP 對應(call,_,_,P)gotoL 對應(j,_,_,L)ifxropygotoL 對應(jrop,x,y,L)例如,賦值語句a=b*(c+d)相應的四元式代碼為①(+,c,d,t1)②(*,b,t1,t2)③(=,t2,_,a)
我們約定:凡只需一個運算量的算符一律使用arg1。此外,注意這樣一個規(guī)則:如果op是一個算術或邏輯運算符,則result總是一個新引進的臨時變量,它用來存放運算結果。由上例也可看出,四元式出現(xiàn)的順序與表達式計值的順序是一致的,四元式之間的聯(lián)系是通過臨時變量實現(xiàn)的。
四元式由于其表示更接近程序設計的習慣而成為一種普遍采用的中間代碼形式。4.4表達式及賦值語句的翻譯簡單算術表達式和賦值語句的翻譯布爾表達式的翻譯4.4.1簡單算術表達式和賦值語句的翻譯簡單算術表達式是一種僅含簡單變量的算術表達式。簡單變量是指普通變量和常數(shù),但不含數(shù)組元素及結構引用等復合型數(shù)據(jù)結構。簡單算術表達式的計值順序與四元式出現(xiàn)的順序相同,因此很容易將其翻譯成四元式形式??紤]以下文法G[A]:A→i=E?? E→E+E∣E*E∣?E∣(E)∣i在此,非終結符A代表“賦值句”。文法G[A]雖然是一個二義文法,但通過確定運算符的結合性及規(guī)定運算符的優(yōu)先級就可避免二義性的發(fā)生。為了實現(xiàn)由表達式到四元式的翻譯,需要給文法加上語義子程序,以便在進行歸約的同時執(zhí)行對應的語義子程序。語義子程序所涉及的語義變量、語義過程及函數(shù)說明如下:
(1)對非終結符E定義語義變量E.place,即用E.place表示存放E值的變量名在符號表中的入口地址或臨時變量名的整數(shù)碼。
(2)定義語義函數(shù)newtemp(?),即每次調用newtemp(?)時都將回送一個代表新臨時變量的整數(shù)碼;臨時變量名按產生的順序可設為T1、T2……
(3)定義語義過程emit(op,arg1,arg2,result),emit的功能是產生一個四元式并填入四元式表中。
(4)定義語義函數(shù)lookup(),其功能是審查是否出現(xiàn)在符號表中,是則返回在符號表的入口指針,否則返回NULL。使用上述語義變量、過程和函數(shù),可寫出文法G[A]中的每一個產生式的語義子程序。
(1)?A→i=E {p=lookup(); if(p==NULL)error(?); elseemit(=,E.place,_,P);}
(2)?E→E(1)+E(2) {E.place=newtemp(?); emit(+,E(1).place,E(2).place,E.place);}
(3)?E→E(1)*E(2)? {E.place=newtemp(?); emit(*,E(1).place,E(2).place,E.place);}
(4)?E→?E(1) {E.place=newtemp(?); emit(uminus,E(1).place,_,E.place);}
(5)?E→(E(1))? {E.place=E(1).place;}
(6)?E→i
? {p=lookup();
if(p!=NULL)E.place=p;/*另一種表示為E.place=entry(i)*/ elseerror(?);}
例4.2
試分析賦值語句X=?B*(C+D)的語法制導翻譯過程。
[解答]賦值語句X=?B*(C+D)的語法制導翻譯過程如表4.2所示(加工分析過程參考表4.1)。4.4.2布爾表達式的翻譯在程序語言中,布爾表達式一般由運算符與運算對象組成。布爾表達式的運算符為布爾運算符,即┐、∧、∨,或為not、and和or(注:C語言中為!、&&和|?|),其運算對象為布爾變量,也可為常量或關系表達式。關系表達式的運算對象為算術表達式,其運算符為關系運算符<、<=、==、!=、>=、>等。關系運算符的優(yōu)先級相同但不得結合,其運算優(yōu)先級低于任何算術運算符。布爾運算符的運算順序一般為┐、∧、∨,且∧和∨服從左結合。布爾算符的運算優(yōu)先級低于任何關系運算符。為簡單起見,我們討論下述文法G[E]生成的布爾表達式:G[E]:E→E∧E∣E∨E∣┐E∣(E)∣i∣iropi
rop代表:<、<=、==、!=、>=、>注意:布爾表達式在程序語言中不僅用作計算布爾值,還作為控制語句(如if-else、while等)的條件表達式,用以確定程序的控制流向。
無論布爾表達式的作用如何,按照程序執(zhí)行的順序,都必須先計算出布爾表達式的值。布爾表達式的翻譯過程:1.畫表達式的翻譯圖;2.寫出每個布爾變量或關系表達式的2個四元式(四元式未完成,未填轉移目標result);3.待整個表達式的四元式寫完,回填轉移目標。計算布爾表達式的值通常有兩種方法。
第一種方法是仿照計算算術表達式的方法,按布爾表達式的運算順序一步步地計算出真假值來。假定邏輯值true用1表示、false用0表示,則布爾表達式1∨(┐0∧0)∨0值的計算過程為: 1∨(┐0∧0)∨0 =1∨(1∧0)∨0 =1∨0∨0 =1∨0 =1
另一種方法是根據(jù)布爾運算的特點實施某種優(yōu)化,即不必一步一步地計算布爾表達式中所有運算對象的值,而是省略不影響運算結果的運算。
例如,在計算A∨B時,
若計算出的A值為1,則B值就無需再計算了;因為不管B的結果是什么,A∨B的值都為1。同理,在計算A∧B時若發(fā)現(xiàn)A值為0,則B值也無需計算,A∧B的值一定為0。如何確定一個表達式的真假出口呢?
考慮表達式E(1)∨E(2),若E(1)為真,則立即知道E也為真,因此,E(1)的真出口也就是整個E的真出口;若E(1)為假,則E(2)必須被計值,此時E(2)的第一個四元式就是E(1)的假出口。當然,E(2)的真假出口也就是整個E的真假出口。
類似的考慮適用于對E(1)∧E(2)的翻譯。我們將E(1)∨E(2)和E(1)∧E(2)的翻譯用圖4–5表示,而對形如┐E(1)的表達式則只需調換E(1)的真假出口就可得到該表達式E的真假出口。圖4–5E(1)∨E(2)和E(1)∧E(2)的翻譯圖(a)?E(1)∨E(2);(b)?E(1)∧E(2)在自下而上的分析過程中,一個布爾式的真假出口往往不能在產生四元式的同時就填上,我們只好把這種未完成的四元式的地址(編號)作為E的語義值暫存起來,待整個表達式的四元式產生完畢之后,再來填寫這個未填入的轉移目標。
對于每個非終結符E,我們需要為它賦予兩個語義值E.tc和E.fc,以分別記錄E所對應的四元式需要回填“真”、“假”出口的四元式地址所構成的鏈。這是因為在翻譯過程中,常常會出現(xiàn)若干轉移四元式轉向同一個目標但目標位置又未確定的情況,此時可用“拉鏈”的方法將這些四元式鏈接起來,待獲得轉移目標的四元式地址時再進行返填。
例如,假定E的四元式需要回填“真”出口的有p、q、r這三個四元式,則它們可鏈接成如圖4–6所示的一條真值鏈(記作tc)。圖4–6拉鏈法鏈接四元式示意為了處理E.tc和E.fc這兩項語義值,我們需要引入如下的語義變量和函數(shù):
(1)nxq:始終指向下一條將要產生的四元式的地址(序號),其初值為1。每執(zhí)行一次emit語句后,nxq自動增1。
emit(op,arg1,arg2,result),emit的功能是產生一個四元式并填入四元式表中。
(2)merge(p1,p2):把以p1和p2為鏈首的兩條鏈合并為一條以p2為鏈首的鏈(即返回鏈首值p2)。
(3)Backpatch(p,t):把鏈首p所鏈接的每個四元式的第四區(qū)段(即result)都改寫為地址t。merge(?)函數(shù)如下:merge(p1,p2){if(p2==0)return(p1);else{p=p2;while(四元式p的第四區(qū)段內容不為0)p=四元式p的第四區(qū)段內容;把p1填進四元式p的第四區(qū)段;
return(p2);}}Backpatch(?)函數(shù)如下:Backpatch(p,t){Q=p;while(Q!=0){q=四元式Q的第四區(qū)段內容;把t填進四元式Q的第四區(qū)段;
Q=q;}}為了便于實現(xiàn)布爾表達式的語法制導翻譯,并在掃描到“∧”與“∨”時能及時回填一些已經確定了的待填轉移目標,我們將
文法G[E]改寫為下面的文法G‘[E],以利于編制相應的語義子程序:
G[E]:E→E∧E∣E∨E∣┐E∣(E)∣i∣iropi
G‘[E]:
E→EAE∣EBE∣┐E∣(E)∣i∣iropi EA→E∧ EB→E∨這時,文法G'[E]的每個產生式和相應的語義子程序如下:
(1)E→i {E.tc=nxq;E.fc=nxq+1; emit(jnz,entry(i),_,0); emit(j,_,_,0);}
(2)E→i(1)ropi(2) {E.tc=nxq;E.fc=nxq+1; emit(jrop,entry(i(1)),entry(i(2)),0); emit(j,_,_,0);}
(3)E→(E(1)) {E.tc=E(1).tc;E.fc=E(1).fc;}
(4)E→┐E(1) {E.tc=E(1).fc;E.fc=E(1).tc;}
(5)EA→E(1)∧
{Backpatch(E(1).tc,nxq); EA.fc=E(1).fc;}
(6)E→EAE(2) {E.tc=E(2).tc; E.fc=merge(EA.fc,E(2).fc);}
(7)EB→E(1)∨
{Backpatch(E(1).fc,nxq); EB.tc=E(1).tc;}
(8)E→EBE(2) {E.fc=E(2).fc; E.tc=merge(EB.tc,E(2).tc);}注意:根據(jù)上述語義動作,在整個布爾表達式所對應的四元式全部產生之后,作為整個表達式的真假出口(轉移目標)仍尚待回填。此外,由產生式(2)也可看出關系表達式的優(yōu)先級要高于布爾表達式。例4.3
試給出布爾表達式a∧b∨c≥d作為控制條件的四元式中間代碼。
[解答]
設四元式序號從100開始,則布爾表達式a∧b∨c≥d的分析過程如圖4–7所示。這時,文法G'[E]的每個產生式和相應的語義子程序如下:
(1)E→i {E.tc=nxq;E.fc=nxq+1;
emit(jnz,entry(i),_,0);
emit(j,_,_,0);}
(2)E→i(1)ropi(2) {E.tc=nxq;E.fc=nxq+1;
emit(jrop,entry(i(1)),entry(i(2)),0);
emit(j,_,_,0);}
(3)E→(E(1)) {E.tc=E(1).tc;E.fc=E(1).fc;}
(4)E→┐E(1) {E.tc=E(1).fc;E.fc=E(1).tc;}
(5)EA→E(1)∧
{Backpatch(E(1).tc,nxq); EA.fc=E(1).fc;}
(6)E→EAE(2) {E.tc=E(2).tc; E.fc=merge(EA.fc,E(2).fc);}
(7)EB→E(1)∨
{Backpatch(E(1).fc,nxq); EB.tc=E(1).tc;}
(8)E→EBE(2) {E.fc=E(2).fc; E.tc=merge(EB.tc,E(2).tc);}圖4–7表達式a∧b∨c≥d分析示意即:100(jnz,a,_,102)/*a為T*/101(j,_,_,104)/*a為F*/102(jnz,b,_,106)/*b為T*/103(j,_,_,104)/*b為F*/104(j≥,c,d,106)/*c≥d為T*/105(j,_,_,q)/*c≥d為F*/T:106/*“真”出口*/
F:q/*“假”出口*/當然,我們也可以通過圖4–8的分析得到上述四元式序列。圖4–8a∧b∨c≥d的翻譯圖由例4.3可知,每一個布爾變量a都對應一真一假兩個四元式,并且格式是固定的,即
(jnz,a,_,0) /*a為布爾變量*/(j,_,_,0)而每一個關系表達式同樣對應一真一假兩個四元式,其格式也是固定的,即
(jrop,X,Y,0)/*X、Y為關系運算符兩側的變量或值*/(j,_,_,0)例4.4
試給出布爾表達式a∨m≠n∨c∧x>y的四元式代碼。
[解答]該布爾表達式的翻譯圖如圖4–9所示,所對應的四元式代碼如下:
100(jnz,a,_,106)/*a為T*/101(j,_,_,102)/*a為F*/102(j≠,m,n,106)/*m≠n為T*/103(j,_,_,104)/*m≠n為F*/104(jnz,c,_,106)/*c為T*/105(j,_,_,q)/*c為F*/106(j>,x,y,108)/*x>y為T*/107(j,_,_,q)/*x>y為F*/T:108/*“真”出口*/
F:q/*“假”出口*/圖4–9例4.4的翻譯圖4.5控制語句的翻譯條件語句if的翻譯條件循環(huán)語句While的翻譯三種基本控制結構的翻譯多分支控制語句case的翻譯語句標號和轉移語句的翻譯在源程序中,控制語句用于實現(xiàn)程序流程的控制。一般程序流程控制可分為下面三種基本結構:
(1)順序結構,一般用復合語句實現(xiàn);
(2)選擇結構,用if和case等語句實現(xiàn);
(3)循環(huán)結構,用for、while、do(即repeat)等語句實現(xiàn)??刂普Z句的翻譯過程:1.畫語句的代碼結構圖;2.根據(jù)代碼結構圖,寫四元式;(四元式未完成,未填轉移目標result)3.翻譯過程(即掃描語句過程)中,伺機回填轉移目標。4.5.1條件語句if的翻譯
1.條件語句if的代碼結構
2.條件語句if的文法和語義子程序的設計1.條件語句if的代碼結構我們按下面的條件語句if的模式進行討論:
if(E)S1;elseS2條件語句if(E)S1;elseS2中布爾表達式E的作用僅在于控制對S1和S2的選擇,因此可將作為轉移條件的布爾式E賦予兩種“出口”:一是“真”出口,出向S1;一是“假”出口,出向S2。于是,條件語句可以翻譯成如圖4–10所示的代碼結構。圖4–10條件語句if(E)S1;elseS2的代碼結構我們知道,非終結符E具有兩項語義值E.tc和E.fc,它們分別指出了尚待回填真假出口的四元式串。E的“真”出口只有在掃描完布爾表達式E后的“)”時才能知道,而它的“假”出口則需要處理過S1之后并且到else時才能明確。這就是說,必須把E.fc的值傳下去,以便到達相應的else時才進行回填。S1語句執(zhí)行完就意味著整個if-else語句已執(zhí)行完畢,因此,在S1的編碼之后應產生一條無條件轉移指令,這條轉移指令將導致程序控制離開整個if-else語句。但是,在完成S2的翻譯之前,這條無條件轉移指令的轉移目標是不知道的,甚至在翻譯完S2之后仍無法確定,這種情形是由語句的嵌套性所引起的。例如下面的語句:
if(E1)if(E2)S1;elseS2;elseS3在S1代碼之后的那條無條件轉移指令不僅應跨越S2,而且應跨越S3。這也就是說,轉移目標的確定和語句所處的環(huán)境密切相關。
2.條件語句if的文法和語義子程序的設計條件語句if的文法G[S]如下:
G[S]:S→if(E)S(1) S→if(E)S(1);elseS(2)為了在掃描條件語句過程中不失時機地處理和回填有關信息,可將G[S]改寫為如下的G'[S]:
G'[S]:(1)?S→CS(1)
(2)?C→if(E)
(3)?S→TpS(2)
(4)?TP→CS(1);else根據(jù)程序語言的處理順序,首先用產生式(2)C→if(E)進行歸約,這時E的真出口即為E所生成四元式序列后的下一個地址。因此,將“)”后的第一個四元式地址回填至E的真出口,E的假出口地址則作為待填信息放在C的語義變量C.chain中,即
C→if(E) {Backpatch(E.tc,nxq); C.chain=E.fc;}接下來用產生式(1)S→CS(1)繼續(xù)向上歸約。這時已經處理到S→if(E)S(1),由于歸約時E的真出口已經處理,而E的假出口(即語句S的出口)同時是語句S(1)的出口,但此時語句S的出口地址未定,故將C.chain和S(1).chain一起作為S的待填信息鏈用函數(shù)merge鏈在一起保留在S的語義值S.chain中,即有
S→CS(1){S.chain=merge(C.chain,S(1).chain)}如果此時條件語句為不含else的條件句,則在產生式(1)、(2)歸約為S后即可以用下一個將要產生的四元式地址(即S的出口地址)來回填S的出口地址鏈(即S.chain);如果此時條件語句為if-else形式,則繼續(xù)用產生式(4)TP→CS(1);else歸約。用Tp→CS(1);else歸約時首先產生S(1)語句序列之后的一個無條件轉移四元式(以便跳過S(2),見圖4–10的結構框圖),該四元式的地址(即標號)保留在q中,以便待獲知要轉移的地址后再進行回填,也即(i)(S(1)的第一個四元式) /*E的真出口*/
(q?1)(S(1)的最后一個四元式)(q)(j,_,_,0)/*無條件跳過S(2),其轉移地址有待回填*/(q+1)(S(2)的第一個四元式) /*E的假出口*/此時q的出口也就是整個條件語句的出口,因此應將其與S.chain鏈接后掛入鏈頭為Tp.chain的鏈中。此外,emit產生四元式q后nxq自動加1(即為q+1),其地址即為else后(也即S(2))的第一個四元式地址,它也是E的假出口地址,因此應將此地址回填到E.fc即C.chain中,即有
Tp→CS(1);else{q=nxq;? emit(j,_,_,0);? Backpatch(C.chain,nxq);? Tp.chain=merge(S.chain,q);}最后用產生式(3)S→TpS(2)歸約。S(2)語句序列處理完后繼續(xù)翻譯if語句之后的后繼語句,這時就有了后繼語句的四元式地址,該地址也是整個if語句的出口地址,它與S(2)語句序列的出口一致。由于S(2)的出口待填信息在S(2).chain中,故將Tp.chain與S(2).chain鏈接后掛入鏈頭為S.chain的鏈中,即
S→TpS(2){S.chain=merge(Tp.chain,S(2).chain);}4.5.2條件循環(huán)語句while的翻譯
1.條件循環(huán)語句while的代碼結構
2.條件循環(huán)語句while的文法和語義子程序設計1.條件循環(huán)語句while的代碼結構條件循環(huán)語句while(E)S(1)通常被翻譯成如圖4–11所示的代碼結構。布爾表達式E的“真”出口出向S(1)代碼段的第一個四元式,緊接S(1)代碼段之后應產生一條轉向測試E的無條件轉移指令;而E的“假”出口將導致程序控制離開整個while語句而去執(zhí)行while語句之后的后繼語句。圖4–11條件循環(huán)語句while的代碼結構注意:E(1)的“假”出口目標即使在整個while語句翻譯完之后也未必明確。例如:
if(E1)while(E2)S1;elseS2
這種情況仍是由于語句的嵌套性引起的,所以只好把E的假出口作為條件循環(huán)語句的語義值S.chain保留下來,以便在處理外層語句時再伺機回填。
2.條件循環(huán)語句while的文法和語義子程序設計同樣,我們給出易于及時處理和回填的條件循環(huán)語句while的文法G[S]如下:
G[S]:(1)?S→WdS(1)
(2)?Wd→W(E)
(3)?W→while根據(jù)while語句的掃描加工順序,首先用產生式(3)W→while進行歸約,這時nxq即為E的第一個四元式地址,我們將其保留在W.quad中。然后繼續(xù)掃描并用Wd→W(E)歸約,即掃描完“)”后可以用Backpatch(E.tc,nxq)回填E.tc值;而E.fc則要等到S(1)語句序列全部產生后才能回填,因此E.fc作為待填信息用Wd.chain=E.fc傳下去。當用產生式(1)S→WdS(1)歸約時,S(1)語句序列的全部四元式已經產生。根據(jù)圖4–11while語句代碼結構的特點,此時應無條件返回到E的第一個四元式繼續(xù)對條件E進行測試,即形成四元式(j,_,_,Wd.quad),同時用Backpatch(S(1).chain,Wd.quad)回填E的入口地址到S(1)語句序列中所有需要該信息的四元式中。在無條件轉移語句(j,_,_,Wd.quad)之后即為while語句的后繼語句,而這個后繼語句中的第一個四元式地址即為while語句E的假出口,保存在Wd.chain中??紤]到嵌套情況,將Wd.chain信息作為整個while語句的出口保留在S.chain中,以便適當時機回填。因此,文法G[S]對應的語義加工子程序如下:
(1)?W→while {W.quad=nxq;}
(2)?Wd→W(E)? {Backpatch(E.tc,nxq); Wd.chain=E.fc; Wd.quad=W.quad;}
(3)?S→WdS(1){Backpatch(S(1).chain,Wd.quad);
emit((j,_,_,Wd.quad);
S.chain=Wd.chain;}當然,我們還可按同樣方法得到doS(1)while(E)條件語句的文法及語義加工子程序。4.5.3三種基本控制結構的翻譯
1.三種基本控制結構的文法
2.翻譯示例1.三種基本控制結構的文法我們給出三種基本控制結構的文法G[S]如下:
G[S]: (1)?S→CS (2)?∣TPS (3)?∣WdS (4)?∣{L} (5)?∣A/*A代表賦值語句*/
(6)?L→LSS (7)?∣S (8)?C→if(E) (9)?TP→CS;else (10)?W→while (11)?Wd→W(E) (12)?LS→L;
G[S]中各產生式對應的語義子程序如下:
(1)?S→CS(1){S.chain=merge(C.chain,S(1).chain);}
(2)?S→TPS(2)?{S.chain=merge(TP.chain,S(2).chain);}
(3)?S→WdS(1){Backpatch(S(1).chain,Wd.quad);
emit(j,_,_,Wd.quad);
S.chain=Wd.chain;}
(4)?S→{L}?{S.chain=L.chain;}
(5)?S→A?{S.chain=0;/*空鏈*/}
(6)?L→LSS(1){L.chain=S(1).chain;}
(7)?L→S
{L.chain=S.chain;}
(8)?C→if(E){Backpatch(E.tc,nxq);
C.chain=E.fc;}(9)?TP→CS(1);else?{q=nxq; emit(j,_,_,0); Backpatch(C.chain,nxq); TP.chain=merge(S(1).chain,q);}
(10)?W→while {W.quad=nxq;}
(11)?Wd→W(E) {Backpatch(E.tc,nxq); Wd.chain=E.fc; Wd.quad=W.quad;}
(12)?LS→L; {Backpatch(L.chain,nxq);}
2.翻譯示例例4.5
將下面的語句翻譯成四元式:
if(x>y)if(a∧b)m=m+1;elsem=m-1;elsex=y;
[解答]該語句對應的代碼結構圖如圖4–12所示,它所對應的四元式序列如下:100(j>,x,y,102)/*x>y為T*/101(j,_,_,110)/*x>y為F*/102(jnz,a,_,104)/*a為T*/103(j,_,_,108)/*a為F*/104(jnz,b,_,106)/*b為T*/105(j,_,_,108)/*b為F*/106(+,m,1,m)/*m=m+1*/107(j,_,_,109)/*內層else之前的跳轉*/108(_,m,1,m)/*m=m-1*/109(j,_,_,111)/*外層else之前的跳轉*/110(=,y,_,x)/*x=y*/111/*出口*/圖4–12例4.5的代碼結構圖
如果按圖4–13所示的翻譯圖進行翻譯,則第107句四元式為(j,_,_,111),雖然與上面的107句不同,但仔細分析就會發(fā)現(xiàn):翻譯圖與代碼結構圖兩者所翻譯的結果在功能上是完全相同的。圖4–13例4.5的翻譯圖
例4.6
將下面的語句翻譯成四元式:while(A<B) if(C<D)X=Y+Z
[解答]我們首先畫出該語句對應的代碼結構圖如圖4–14所示。
圖4–14例4.3的代碼結構圖按照文法及加工子程序(包括前述賦值句和布爾表達式的翻譯法)得到該語句對應的四元式序列如下:100(j<,A,B,102) /*E1為T*/101(j,_,_,107) /*E1為F*/
102(j<,C,D,104) /*E2為T*/103(j,_,_,106) /*E2為F*/104(+,Y,Z,T)/*T為臨時變量,保存Y+Z*/105(=,T,_,X)/*X=T*/106(j,_,_,100)/*循環(huán)回跳*/
107/*出口*/例4.7
將下面的語句翻譯成四元式:if(a∧b)
while(x<y)
if(m≠n)
m=n;elsem=m+1;else
while(m>n)x=x+y;
[解答]我們首先畫出該語句對應的代碼結構圖如圖4–15所示。圖4–15例4.4的代碼結構圖雖然根據(jù)文法及加工子程序可以得到該語句對應的四元式序列,但我們知道每個布爾變量及每個關系表達式都對應一真一假固定格式的兩個四元式,且if語句在else之前應有一無條件轉移語句跳過else后的語句,而while語句則在結束時有一個向回跳的無條件轉移語句。由本題可知,共有兩個布爾變量a、b和三個關系表達式,共需10條四元式,而兩個if語句和兩個while語句共需4條無條件轉移四元式,再加上3條賦值四元式,總計為17條四元式。再依據(jù)代碼結構圖來確定各個條件轉移和無條件轉移四元式的轉移地址,就很容易得到該語句的四元式序列如下:100(jnz,a,_,102)/*a為T*/101(j,_,_,113)/*a為F*/102(jnz,b,_,104)/*b為T*/103(j,_,_,113)/*b為T*/104(j<,x,y,106)/*x<y為T*/105(j,_,_,112)/*x<y為F*/106(j≠,m,n,108)/*m≠n為T*/107(j,_,_,110)/*m≠n為F*/108(=,n,_,m)/*m=n*/109(j,_,_,111)/*內層else之前跳轉*/110(+,m,1,m)/*m=m+1*/111(j,_,_,104)/*循環(huán)回跳*/112(j,_,_,117)/*外層else之前跳轉*/113(j>,m,n,115)/*m>n為T*/114(j,_,_,117)/*m>n為F*/115(+,x,y,x)/*x=x+y*/116(j,_,_,113)/*循環(huán)回跳*/117/*出口*/例4.8
按已學過的文法及語義加工子程序分析下述語句語義加工的全過程:
while(x<y)x=x+1
[解答]語句while(x<y)x=x+1的語義加工過程見表4.3。4.5.4多分支控制語句case的翻譯多分支控制語句具有如下形式的語法結構:switch(E){ casec1:S1;casec2:S2;caseci:Si;casecn:Sn;default:Sn+1}其中n≥1。switch語句的語義是:先計算整型表達式E的值,然后將表達式的值依次和case后的常數(shù)ci比較,當與某常數(shù)ci相等時就執(zhí)行語句Si,并結束多分支控制語句;若與諸常數(shù)均不相等,則執(zhí)行語句Sn+1。多分支控制語句switch常見的中間代碼形式如下:
E計值后存放在臨時單元T的中間代碼;
gototest;
P1: S1的中間代碼;
gotonext;
P2: S2的中間代碼;
gotonext;
Pn: Sn的中間代碼;
gotonext;
Pn+1: Sn+1的中間代碼;
gotonext;
test: if(T==c1)gotoP1;
if(T==c2)gotoP2;
if(T==cn)gotoPn;
if(T=='default')gotoPn+1;
next:進行語義加工處理時應先設置一空隊列queue,當遇到ci時,將這個ci連同nxq(指向標號ci后語句Si的入口)送入隊列queue,然后按通常的辦法產生語句Si的四元式。需要注意的是,在Si的四元式之后要有一個gotonext的四元式。當處理完default:Sn+1之后,應產生以test為標號的n個條件轉移語句的四元式。這時,逐項讀出queue的內容即可形成如下的四元式序列:
(case,c1,P1,_?) (case,c2,P2,_?) (case,cn,Pn,_?) (case,T.place,default,_?)其中,T.place是存放E值的臨時變量名,每個四元式(case,ci,Pi,_?)實際上代表一個如下的條件語句:
if(T==ci)gotoPi為了便于語法制導翻譯,我們給出了switch語句的文法和相應的語義加工子程序如下:
(1)?A→switch(E)?{T.place=E.place;
F1.quad=nxq;
emit(j,_,_,0); /*轉向test*/}
(2)?B→A{casec?{P=1;
queue[P].label=c;
queue[P].quad=nxq;}
(3)?D→B:S{生成S的四元式序列;
Backpatch(S.chain,nxq);
B.quad=nxq;
emit(j,_,_,0); /*轉向next*/}
(4)?D→F:S{生成S的四元式序列;
Backpatch(S.chain,nxq);
B.quad=nxq;
emit(j,_,_,0); /*轉向next*/ F.quad=merge(B.quad,F.quad);
/*轉向next的語句拉成鏈*/}
(5)?F→D;casec?{P=P+1;
queue[P].label=c;
queue[P].quad=nxq;}
(6)?S→D;default:S} {生成S的四元式序列;
Backpatch(S.chain,nxq);
B.quad=nxq;
emit(j,_,_,0);
F.quad=merge(B.quad,F.quad);
/*形成轉向next的鏈首*/} P=P+1;
queue[P].label='default';
queue[P].quad=nxq;
F3.quad=nxq;/*指向標號test*/ m=1; do { ci=queue[m].label;
Pi=queue[m].quad;
m=m+1;
if(ci!='default')emit(case,ci,Pi,_) elseemit(case,T.place,default,_) }while(m<=P+1);
Backpatch(F1.quad,F3.quad);
Backpatch(F.quad,nxq);
/*填寫所有轉向next語句的轉移地址*/}4.5.5語句標號和轉移語句的翻譯程序語言中直接改變控制流程的語句是gotoL語句,其中L是源程序中的語句標號。標號L在源程序中可以以兩種方式出現(xiàn):
(1)定義性出現(xiàn)。定義性出現(xiàn)的語句形式為
L:S此時,帶標號的語句S所生成的第一個四元式地址即為標號L的值。
(2)引用性出現(xiàn)。引用性出現(xiàn)的語句形式為
gotoL它引用L的值作為四元式(j,_,_,L)中轉向的目標地址。對標號L的處理方法是:當標號L定義性出現(xiàn)時,應將標號此時對應的四元式地址(即標號L的值)登錄到符號表中L所對應的項;當標號L引用性出現(xiàn)時,則引用符號表中該標號L的值。顯然,在源程序中,如果標號的定義性出現(xiàn)在前而引用性出現(xiàn)在后,即先定值后引用(稱為向后引用),則填、查符號表及將轉移語句翻譯成四元式很容易。但是,如果標號引用性出現(xiàn)在前而定義性出現(xiàn)在后(稱為向前引用),則引用時不可能從符號表中獲得標號L的值,此時只能生成有待回填的四元式(j,_,_,0),等到向前翻譯到標號L定義性出現(xiàn)時,再將標號L的值回填到待填的四元式中。翻譯gotoL語句時需要查符號表,看L是否定值,有以下幾種情況:
(1)?L已經定值,即L.value為符號表中所記錄的L值,這時生成(j,_,_,L.value)語句。
(2)在符號表中未出現(xiàn)標號L項,則gotoL中的L是首次出現(xiàn),故生成(j,_,_,0)形式的語句并在符號表中登錄L項,給L項標記為“未定值”并將四元式(j,_,_,0)的地址作為L的值記入符號表(作為L引用鏈的鏈頭),以待L定值后回填。
(3)在符號表中已有標號L項但未定值,此時的gotoL語句并非首次出現(xiàn),故生成四元式(j,_,_,0)并將其地址掛入到L的引用鏈中,待L定值后再進行回填。翻譯語句L:S時,在識別L后也要查符號表。如L為首次出現(xiàn),則在符號表中建立L項,將此時的nxq值作為L的值登入符號表并置“已定值”標記;如果符號表中已有L項且標記為“未定值”,這意味著是向前引用情況,應將此時的nxq值作為L的值登入符號表并置“已定值”,同時以此值回填L的引用鏈;若查找符號表發(fā)現(xiàn)L已定值,則表示L出現(xiàn)了重復定義的錯誤。4.6數(shù)組元素的翻譯數(shù)組元素的地址計算及中間代碼形式賦值語句中數(shù)組元素的翻譯數(shù)組元素翻譯示例4.6.1數(shù)組元素的地址計算及中間代碼形式在表達式或賦值語句中若出現(xiàn)數(shù)組元素,則翻譯時將牽涉到數(shù)組元素的地址計算。數(shù)組在存儲器中的存放方式決定了數(shù)組元素的地址計算法,從而也決定了應該產生什么樣的中間代碼。數(shù)組在存儲器中的存放方式通常有按行存放和按列存放兩種。在此,我們討論以行為主序存放方式的數(shù)組元素地址計算方法。數(shù)組的一般定義為A[l1:u1,l2:u2,…,lk:uk,…,ln:un]其中,A是數(shù)組名,lk是數(shù)組A第k維的下界,uk是第k維的上界。為簡單起見,假定數(shù)組A中每個元素的存儲長度為1,a是數(shù)組A的首地址,則數(shù)組元素A[i1,i2,…in]的地址D的計算公式如下:D=a+(i1?l1)d2d3…dn+(i2?l2)d3d4…dn+…+(in-1?ln?1)dn+(in?ln)其中,di=ui?li+1(i=1,2,…,n?1)。整理后得到D=CONSPART+VARPART其中,CONSPART=a?(…((l1d2+l2)d3+l3)d4+…+ln-1)dn+ln VARPART=(…((i1d2+i2)d3+i3)d4+…+in?1dn)+in
CONSPART中的各項(如li、di(i=1,2,…,n))在處理說明語句時就可以得到,因此CONSPART值可在編譯時計算出來后保存在數(shù)組A的相關符號表項里。此后,在計算數(shù)組A的元素地址時僅需計算VARPART值,而直接引用CONSPART值。實現(xiàn)數(shù)組元素的地址計算時,將產生兩組四元式序列:一組計算CONSPART,其值存放在臨時變量T中;另一組計算VARPART,其值存放在臨時變量T1中,即用T1[T]表示數(shù)組元素的地址。這樣,對數(shù)組元素的引用和賦值就有如下兩種不同的四元式:
(1)變址存數(shù):若有T1[T]=X,則可以用四元式([?]=,X,_,T1[T])表示。
(2)變址取數(shù):若有X=T1[T],則可用四元式(=[?],T1[T],_,X)表示。4.6.2賦值語句中數(shù)組元素的翻譯為了便于語法制導翻譯,我們定義一個含有數(shù)組元素的賦值語句文法G[A]如下:
G[A]:(1)?A→V=E (2)?V→i[elist]∣i (3)?elist→elist,E∣E (4)?E→E+E∣(E)∣V其中,A代表賦值語句;V代表變量名;E代表算術表達式;elist代表由逗號分隔的表達式,它表示數(shù)組的一維下標;i代表簡單變量名或數(shù)組名。在用產生式(2)、(3)進行歸約時,為了能夠及時計算數(shù)組元素的VARPART,我們將產生式(2)、(3)改寫為
(2')?V→elist]∣i (3')?elist→elist,E∣i[E把數(shù)組名i和最左的下標式寫在一起的目的是在整個下標串elist的翻譯過程中隨時都能知道數(shù)組名i的符號表入口,從而隨時能夠了解登記在符號表中有關數(shù)組i的全部信息。為產生計算VARPART的四元式序列,還需要設置如下的語義變量和函數(shù):
(1)?elist.ARRAY:表示數(shù)組名在符號表的入口。
(2)?elist.DIM:計數(shù)器,用來計算數(shù)組的維數(shù)。
(3)?elist.place:登錄已生成VARPART中間結果的單元名字在符號表中的存放位置,或是一個臨時變量的整數(shù)碼。
(4)?limit(ARRAY,k):參數(shù)ARRAY表示數(shù)組名在符號表的入口,k表示數(shù)組當前計算的維數(shù);函數(shù)limit(?)計算數(shù)組ARRAY的第k維長度dk。在逐次對elist歸約的過程中,將逐步產生計算VARPART的四元式。此外,每個變量V有兩項語義值:V.place和V.offset。?若V是一個簡單變量名i,則V.place就是該變量名在符號表中的入口,而V.offset此時為null;若V是一個下標變量名,則V.place是保存CONSPART的臨時變量名的整數(shù)碼,而V.offset則是保存VARPART的臨時變量名的整數(shù)碼。含有數(shù)組元素的賦值語句對應的文法G[A]及相應的語義子程序如下(省略語義檢查,僅給出主要語義動作):
(1)?A→V=E{if(V.offset==null)? emit(=,E.place,_,V.place);/*V是簡單變量*/ elseemit([]=,E.place,_,V.place[V.offset]);
/*V是下標變量*/}
(2)?E→E(1)+E(2)?{T=newtemp;emit(+,E(1).place,E(2).place,T);E.place=T;}
(3)?E→(E(1)){E.place=E(1).place;}
(4)?E→V{if(V.offset==null)? E.place=V.place; /*V是簡單變量*/ else{T=newtemp;/*V是下標變量*/? emit(=[],V.place[V.offset],_,T);
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 中南大學《環(huán)境監(jiān)測技術》2023-2024學年第二學期期末試卷
- 江西洪州職業(yè)學院《建筑學前沿及研究方法》2023-2024學年第二學期期末試卷
- 廣東科學技術職業(yè)學院《技術應用》2023-2024學年第二學期期末試卷
- 湖南大學《給水排水工程》2023-2024學年第二學期期末試卷
- 農產品抖音快手直播合作協(xié)議范本(MCN機構版)
- 南昌航空大學科技學院《跨媒體展演》2023-2024學年第二學期期末試卷
- 重慶工程學院《住宅空間設計》2023-2024學年第二學期期末試卷
- 電子商務與綠色環(huán)保的協(xié)同發(fā)展研究
- 江蘇電子信息職業(yè)學院《生物合成藥物學》2023-2024學年第二學期期末試卷
- 萍鄉(xiāng)學院《機電設備PC控制》2023-2024學年第二學期期末試卷
- “5E”教學模式下高中數(shù)學教學實踐研究
- 《醫(yī)學影像檢查技術學》課件-踝X線攝影
- 急救藥品知識培訓內容
- 電工基礎知識(全套)
- 體育館施工圖設計合同
- 2025年福建省漳州臺商投資區(qū)招聘非占編人員歷年高頻重點提升(共500題)附帶答案詳解
- 四川省成都市成華區(qū)2024年中考語文二模試卷附參考答案
- 《西蘭花全程質量安全控制技術規(guī)范》
- 2025年臨床醫(yī)師定期考核試題中醫(yī)知識復習題庫及答案(200題)
- 2025年臨床醫(yī)師定期考核必考復習題庫及答案(900題)
- 《小紅帽》繪本故事-課件
評論
0/150
提交評論