與傳統(tǒng)語言相比磺送,F(xiàn)orth的編譯器過于簡單估灿。
傳統(tǒng)的編譯器通常設(shè)計(jì)成大型的程序缤剧,用來將可預(yù)見的合法的語法組織轉(zhuǎn)換為機(jī)器語言。
然而Forth的編譯器僅僅使用一個(gè)包含幾行的定義實(shí)現(xiàn)汗销。高級(jí)的語法結(jié)構(gòu)如條件語句和循環(huán)語句則由定義的高級(jí)words再次定義實(shí)現(xiàn)的
拋開對(duì)Forth過于簡單的看法。你會(huì)發(fā)現(xiàn)Forth在擴(kuò)展編譯器方面的獨(dú)特功能弛针。通過定義新的word,F(xiàn)orth非常容易對(duì)底層的編譯器進(jìn)行擴(kuò)展座云。
這種通過擴(kuò)展編譯器的方式付材,可以實(shí)現(xiàn)具有強(qiáng)大的語言功能
1 區(qū)分編譯時(shí)與運(yùn)行時(shí)
在深入理解Forth的編譯器時(shí),我們需要認(rèn)真區(qū)分Forth中的編譯時(shí)與運(yùn)行時(shí)
通常在Forth編程中璧帝,一個(gè)word的運(yùn)行過程(executed)稱為運(yùn)行時(shí)(run time)富寿。一個(gè)Forth的定義過程(compiled)稱為編譯時(shí)(compile time)。然而Forth中有些特定的word既包含運(yùn)行時(shí)(run-time)也包含編譯時(shí)(compile-time)行為
Forth中兩類特定word具有編譯和運(yùn)行時(shí)行為苏潜,下面的討論過程中变勇,我們將這類word看做定義word(defining words)和編譯word(compiling words)
一個(gè)定義word(defining word)在運(yùn)行時(shí)(executed)用來編譯生成一個(gè)新的定義。每個(gè)定義word(defining word)說明了它所定義一類的word所具有的運(yùn)行時(shí)(run-time)和編譯時(shí)(compile-time)行為飞袋。
其中一個(gè)定義word(definiting word)是常量定義wordCONSTANT
链患。
在Forth終端中輸入80 CONSTANT MARGIN
,就會(huì)進(jìn)入CONSTANT
的編譯時(shí)(compile-time)行為纲仍,
CONSTANT
的編譯時(shí)行為將在字典(dictionary)建立一個(gè)常量類型的入口贸毕,并命名為MARGIN。然后將80存儲(chǔ)到該常量的參數(shù)字段锻拘,
在Forth終端輸入MARGIN
,就會(huì)進(jìn)入CONSTANT
的運(yùn)行時(shí)(run-time)行為婉宰,
CONSTANT
的運(yùn)行時(shí)行為會(huì)將MARGIN
對(duì)應(yīng)的常量值存儲(chǔ)到數(shù)字棧(stack)中推穷。
另一類具有雙重行為的word我們稱為編譯word(compiling word)。編譯word通常用來分號(hào)定義中蟹腾,并且在這個(gè)過程的定義中實(shí)現(xiàn)某些特定功能
其中一種編譯word是."
区宇。這個(gè)編譯word在定義的編譯(compile-time)過程中將會(huì)將一段文本字符串的長度與其內(nèi)容寫入到該定義word的入口。在運(yùn)行(run-time)過程中將會(huì)打印輸出這段文本內(nèi)容炉爆。
其他的編譯word如IF
和LOOP
頁同時(shí)包含不同的運(yùn)行時(shí)和編譯時(shí)行為卧晓。
2 定義word(defining word)的實(shí)現(xiàn)
目前為止我們接觸到的定義word(defining word)如下
VARIABLE ; 變量定義
2VARIABLE ; 變量定義
CONSTANT ; 常量定義
2CONSTANT ; 常量定義
: ; 過程定義
CREATE ; 內(nèi)存分配定義
這些定義word都是用來定義具有相似的編譯時(shí)和運(yùn)行時(shí)行為的類words
這些定義word通常為他們所定義一類word實(shí)現(xiàn)了特定的編譯時(shí)(compile-time)和運(yùn)行時(shí)(run-time)行為
其中CREATE
是這類定義word(defining words)中基礎(chǔ)的定義word。
在編譯時(shí)(compile-time),CREATE
會(huì)從輸入流(inpute stream)通過空格截取一個(gè)名稱(EXAMPLE)郁稍,然后在字典中創(chuàng)建一個(gè)變量區(qū)域使用這個(gè)名稱作為開頭胜宇。
在運(yùn)行時(shí)(run-time),CREATE
會(huì)將這個(gè)名稱(EXAMPLE)對(duì)應(yīng)的字典的body的地址存儲(chǔ)到數(shù)字棧(stack)上。
基于CREATE
可以實(shí)現(xiàn)定義wordVARIABLE
封寞。
: VARIABLE CREATE 0 , ;
因此輸入VARIABLE ORANGES
時(shí)
VARIABLE
會(huì)執(zhí)行CREATE
以O(shè)RANGES為名稱在字典中創(chuàng)建變量入口仅财。然后將變量的body地址存儲(chǔ)到數(shù)字棧(stack)
然后0 ,
中的,
會(huì)將0存儲(chǔ)到變量的body中碗淌。
由于VARIABLE
定義的變量由CREATE
實(shí)現(xiàn),因此ORANGES具有與CREATE定義的word相同的運(yùn)行時(shí)(run-time)行為碎罚,VARIABLE
調(diào)用時(shí)將會(huì)將地址存放到數(shù)字棧(stack)上纳像。
通常在定義word(deining word)中我們使用DOES>
區(qū)別word的運(yùn)行時(shí)(run-time)行為
: DEFINING_WORD
CREATE (compile-time operations)
DOES> (run-time operations)
為了證實(shí)這點(diǎn),我們可以將CONSTANT
定義word實(shí)現(xiàn)為如下結(jié)構(gòu)
: CONSTANT
CREATE 憔购, (CREATE 創(chuàng)建一個(gè)字典入口,存儲(chǔ)數(shù)字棧的值到常量參數(shù)字段)
DOES> @; (DOES 將word的body地址存儲(chǔ)到stack中导绷,然后調(diào)用@,獲取數(shù)字棧上地址的內(nèi)容)
那么在Forth中輸入76 CONSTANT TROMEBONES
將會(huì)實(shí)現(xiàn)如上定義過程
而DOES>
前綴修改模式屎飘,其后的內(nèi)容定義這個(gè)模式的操作內(nèi)容
DOES>
用在創(chuàng)建定義word(defining word)標(biāo)記編譯時(shí)行為結(jié)束和運(yùn)行時(shí)行為開始。運(yùn)行時(shí)行為常常是Forth中高層的操作檐盟。這個(gè)body地址將會(huì)存儲(chǔ)到stack中
3 定義word實(shí)現(xiàn)實(shí)例
接下來舉例說明定義word的實(shí)現(xiàn)
在字符串輸入中押桃,我們需要將輸入內(nèi)容的長度和輸入內(nèi)存存儲(chǔ)到變量中。
我們可以定義相應(yīng)的word簡化這種操作
這里我們定義wordCHARACTERS
如果我們輸入20 CHARACTERS ME
那么我們將創(chuàng)建一個(gè)名為ME的可以用來存儲(chǔ)20個(gè)字符的字符串?dāng)?shù)組
如果我們?cè)俅屋斎?code>ME,我們可以獲取這個(gè)字符串?dāng)?shù)組的地址和字符串中字符的長度存儲(chǔ)到數(shù)字棧(stack)在陌宿。
CHARACTER
的實(shí)現(xiàn)如下
: CHARCTERS
CREATE (定義時(shí)創(chuàng)建字典ME入口)
DUP , ALLOT (首先(DUP ,)復(fù)制20將其存儲(chǔ)到body首個(gè)位置波丰,然后(ALLOT)申請(qǐng)20個(gè)用來存儲(chǔ)字符的內(nèi)存)
DOES> (運(yùn)行時(shí)獲取body地址到stack)
DUP (復(fù)制數(shù)字棧上的body地址)
CELL+ (移動(dòng)到字符串長度下一個(gè)內(nèi)存地址,也就是字符串內(nèi)容首地址到數(shù)字棧,)
SWAP (然后交換字符串地址棧與長度地址爽蝴,)
@ (獲取字符串長度到數(shù)字棧頂部)
; (運(yùn)行完后數(shù)字棧的內(nèi)容為 (addr count --))
這里我們擴(kuò)展編譯器實(shí)現(xiàn)了一個(gè)CHARACTERS
定義word纫骑。這個(gè)定義word將會(huì)創(chuàng)建特定的數(shù)據(jù)結(jié)構(gòu)。不僅僅可以簡化我們的輸入輸出先馆,還可以在需要的時(shí)候用來修改字符串的長度,
接下來我們實(shí)現(xiàn)一個(gè)有用的字符串?dāng)?shù)組定義wordSTRING
: STRING CREATE ALLOT DOES> + ;
輸入30 STRING VALUE
將會(huì)創(chuàng)建一個(gè)名為VALUE的30個(gè)字節(jié)長的數(shù)組梅惯。
當(dāng)我們輸入6 VALUE C@
我們可以訪問特定位置的內(nèi)容
我們還可以實(shí)現(xiàn)其他的數(shù)字?jǐn)?shù)組仿野。比如初始化為0的數(shù)字?jǐn)?shù)組,
: ERASED HERE OVER ERASAE ALLOT ;
: 0STRING CREATE ERASAED DOES> + ;
首先定義ERASED
然后在0STRING
中調(diào)用這個(gè)定義word
可以通過修改定義word葫哗,實(shí)現(xiàn)修改其他由這個(gè)word實(shí)現(xiàn)的功能。
另外我們可以實(shí)現(xiàn)將內(nèi)存中的字符串存儲(chǔ)到硬盤中桨螺,只需要重新修改STRING的運(yùn)行時(shí)行為(run-time)酿秸。這個(gè)新的STRING將會(huì)通過計(jì)算機(jī)硬盤中需要包含記錄的長度 然后將去讀取到輸入緩存中,最后然后這個(gè)輸入緩存中的地址
可以使用定義 word創(chuàng)建任何類型的數(shù)據(jù)結(jié)構(gòu)肝箱,有時(shí)候需要?jiǎng)?chuàng)建多維數(shù)組稀蟋,下面給出創(chuàng)建二維數(shù)組的定義
: ARRAY (#row #cols --)
CREATE DUP , * ALLOT
DOES (member : row col -- addr)
ROW OVER@⊥丝汀* +〉挡!+ CELL+ ;
如果輸入4 4 ARRAY BOARD
將會(huì)創(chuàng)建4x4的數(shù)組
為了獲取數(shù)組的內(nèi)容 輸入2 1 BOARD C@
將會(huì)獲取2行1列內(nèi)容
其中運(yùn)行時(shí)(run-time)行為如下
Operation Contents of stack
... row col pfa
ROT col pfa row
OVER @ col pfa row #cols
* col pfa row-index
++ address
CELL+ corrected address
最后一個(gè)例子是一個(gè)可視化定義wordShapes
DECIMAL
: star [CHAR] * EMIT ;
: .row CR8 0 DO
DUP 128 AND IF star
ELSE SPACE
THEN
1 LSHIFT
LOOP DROP;
: SHAPE CREATE 8 0 DO C, LOOP
DOES> DUP 7 + DO I C@ .row -1 +LOOP CR;
4 分號(hào)編譯器的機(jī)制
上面的定義word(defining word)通常用來實(shí)現(xiàn)特定的數(shù)據(jù)結(jié)構(gòu)的保存與獲取误趴,下面的編譯word(compiling word)通常用在分號(hào)定義的編譯器。
最具有代表性的編譯word是實(shí)現(xiàn)邏輯控制的word,如IF
THEN
DO
LOOP
等凉当。因?yàn)檫@些word對(duì)于Forth系統(tǒng)很重要售葡,因此我們一般不會(huì)修改這些word。為了理解這些編譯word楼雹,我們會(huì)在運(yùn)行過程檢測這些words的實(shí)現(xiàn)機(jī)制像寒,然后實(shí)現(xiàn)各種編譯word(compiling-word)
正如在介紹:
時(shí)所說的瓜贾,進(jìn)入:
后搜索各個(gè)word的定義地址,然后編譯各個(gè)word的定義地址到相應(yīng)的word的入口祭芦。
然后在分號(hào)編譯過程中并不會(huì)將編譯word的地址存儲(chǔ)到定義字典中,而是運(yùn)行這種編譯word
那么分號(hào)編譯過程中如何區(qū)分這兩種word? 可以通過檢查word定義的優(yōu)先級(jí)precedence bit
實(shí)現(xiàn)胃夏。如果這個(gè)優(yōu)先級(jí)位是off。那么這個(gè)word的地址將會(huì)存儲(chǔ)到定義字典中照雁,如果這個(gè)優(yōu)先級(jí)位是on答恶,那么可以理解執(zhí)行這個(gè)word。這種立即執(zhí)行的word稱為立即word(immediate)悬嗓。
也就是說:
;
等word是立即word
可以使用關(guān)鍵詞IMMEDIATE
創(chuàng)建一個(gè)立即word。
: name definition ; IMMEDIATE(設(shè)置 precedence bit 為on)
那么燕酷,這個(gè)word將會(huì)在定義過程被運(yùn)行
下面是個(gè)例子
: SAY-HELLO ." Hello" ; IMMEDIATE
我們可以簡單的運(yùn)行這個(gè)word周瞎,正如普通word
SAY-HELLO return-key
輸出Hello ok
然而神奇的時(shí),如果我們將這個(gè)word存放到另一個(gè)定義中挤渐,如
: GREET SAY-HELLO ." I Speak forth " ;
時(shí)
SAY-HELLO
的地址將不會(huì)存儲(chǔ)到GREET
的定義中双絮,而是直接輸出
Hello ok
.
這個(gè)可以通過輸入GREET
的輸出I Speak forth
得證。
由此可知立即word不會(huì)被存儲(chǔ)到定義word的字典中
這里需要說說Forth使用者對(duì)于這種行為的習(xí)慣稱呼软免。
在上述的GREET例子中,我們認(rèn)為SAY-HEELO有一個(gè)編譯時(shí)行為而沒有運(yùn)行時(shí)行為膏萧,然而對(duì)于單獨(dú)的SAY-HELLO卻包含著運(yùn)行時(shí)行為蝌衔。
通常我們將GREET稱為SAY-HELLO的編譯者,立即word,SAY-HEELO對(duì)于它的編譯者GREET沒有運(yùn)行時(shí)行為
另一個(gè)立即word的例子是BEGIN
: BEGIN HERE ; IMMEDIATE
曹锨。由此可知BEGIN
在編譯時(shí)將HERE的地址存儲(chǔ)到數(shù)字棧stack
剃允。然后在隨后的UNTIL
或者REPEAT
的立即word將會(huì)得到重復(fù)時(shí)需要返回的地址齐鲤,也就是BEGIN
存儲(chǔ)到數(shù)字棧的地址椒楣。
BEGIN
并不會(huì)存儲(chǔ)任何內(nèi)容到對(duì)應(yīng)的word。只是簡單的將HERE存儲(chǔ)到stack中供REPEAT使用淆九。
然后大多數(shù)編譯word包含一個(gè)運(yùn)行時(shí)行為毛俏,對(duì)于這類編譯word,通常需要將其運(yùn)行行為入口地址存儲(chǔ)到編譯word中拧抖。
一個(gè)具有代表性的例子是DO
。
與BEGIN
相同的是DO
在編譯時(shí)需要提供HERE
供LOOP
或者+LOOP
使用來返回到重復(fù)運(yùn)行的地址擦盾。
不過與BEGIN
不同的是DO
也包含一個(gè)運(yùn)行時(shí)行為淌哟,會(huì)將limit和index存儲(chǔ)到return stack中
DO
的運(yùn)行時(shí)行為使用Forth在低級(jí)word定義,
: DO POSTONE 2>R HERE ; IMMEDIATE
其中的POSTONE
會(huì)查找接下來的定義中的word(2>R)腐碱。然后將其存儲(chǔ)到編譯定義中。因此在運(yùn)行時(shí)2>R
將會(huì)被運(yùn)行症见。也就是用來存儲(chǔ)limit和index到return stack中殃饿。
可以將POSTONE看做IMMEDIATE的臨時(shí)取消動(dòng)作,
另一個(gè)例子是;
遵蚜。在編譯過程中奈惑,分號(hào)的操作內(nèi)容如下
; POSTPONE EXIT REVEAL POSTONE [ ; IMMEDIATE
首先將EXIT的地址存儲(chǔ)到;
中,作為運(yùn)行時(shí)的行為肴甸,
然后使用REVEAL
將當(dāng)前編譯的;
暴漏出來可以被用于其后的定義中
REVEAL
會(huì)將正在被編譯生成的word可以在編譯過程中搜索到。
POSTPONE
會(huì)將立即word強(qiáng)制編譯到word中而不是運(yùn)行它
其運(yùn)行機(jī)制是解析其后的輸入字符流中的word不撑,判斷是否是立即word然后執(zhí)行不同的行為晤斩。如果這個(gè)word不是立即word。將會(huì)將這個(gè)word的地址編譯存儲(chǔ)到定義字典中澳泵,如果這個(gè)word是立即word,那么會(huì)強(qiáng)制將立即word的地址編譯到當(dāng)前定義字典中兔辅。然而在退出的定義中,一個(gè)立即word會(huì)被運(yùn)行碰辅。
下面是IMMEDIATE和POSTPONE的機(jī)制
IMMEDIATE 標(biāo)注定義的word為立即word
POSTPONE 在編譯word中介时,直接編譯接下來的word地址到定義中,
5 更多的編譯控制words
還有兩個(gè)需要了解的編譯控制word沸柔。[
]
可以用在分號(hào)定義中停止編譯和重新開啟編譯。在它們之間包含的word將會(huì)被看做立即word運(yùn)行
: SAY-HELLO ." Hello " ;
: GREET [ SAY-HELLO ] ." I speak forth " ; (輸出 Hello ok)
GREET 輸出(I speak Forth ok)
SAY-HELLO并不是一個(gè)立即word会钝,但是[]
可以修改其編譯控制工三,當(dāng)做立即word運(yùn)行
這種機(jī)制的代表例子是word LITERAL
數(shù)字在分號(hào)定義中會(huì)被看做字面量(literal),比如數(shù)字4
: FOUR-MORE 4 +
;會(huì)將數(shù)字4看做字面量
字面量在過程定義(:
)中需要兩個(gè)cells保存胁出,如下
首先保存LITERAL的word將會(huì)在運(yùn)行將數(shù)字4存儲(chǔ)到數(shù)字棧stack中
可以將這種行為成為字面量運(yùn)行時(shí)代碼段审。或者簡單看做字面量行為
因此過程定義:
首先會(huì)將字面量行為word存儲(chǔ)抑淫,然后存儲(chǔ)數(shù)字本身
LITERAL
會(huì)將字面量代碼和數(shù)字本身存儲(chǔ)到定義中
: FOUR-MORE [ 4 ] LITERAL + ;
首先會(huì)將數(shù)字4當(dāng)做立即word運(yùn)行姥闪,存儲(chǔ)到數(shù)字棧(stack)。然后保存運(yùn)行時(shí)代碼與數(shù)字4到定義中.會(huì)得到與上面相同的FOUR-MORE的定義
另一個(gè)LITERAL的使用如下
VARIABLE LIMITS 4 CELLS ALLOT
可以創(chuàng)建一個(gè)LIMIT
: LIMIT (index -- addr) CELLS LIMITIS + ;
HERE 5 CELLS ALLOT BASAE !
首先我們將HERE的地址存儲(chǔ)到BASE中筐喳。然后定義如下的LIMIT
: LIMIT (index -- addr) CELLS [BASE @] LITERAL +
;
可以將這個(gè)地址作為字面量存儲(chǔ)到LIMIT中函喉,然后恢復(fù)BASE
DECIMAL
目前我們已經(jīng)清楚了LITERAL管呵。我們給出一個(gè)關(guān)于[,]
更好的例子
假設(shè)在過程定義:
中哺窄,我們需要打印數(shù)組 我們上面定義的BOARD的row 2 column 3。為了獲取這個(gè)字節(jié)的地址萌业,我們可以使用下面的句子
BOARD 2 8 (#cols) * 3 + CELL+ +
為了將這種語句特例化
可以改寫為BOARD [2 8 ( #cols) * 3 + CELL+] LITERAL +
下面是一個(gè)例子可以用在應(yīng)用中,這些定義可以讓我們一探word的內(nèi)部實(shí)現(xiàn)機(jī)制
`: DUMP-THIS [HERE] LITERAL 32 DUMP . " DUMP-THIS"
運(yùn)行DUMP-THIS時(shí)婴程,將會(huì)打印存儲(chǔ)到DUMP-THIS中的地址抱婉。
而LITERAL
的定義如下
: LITERAL POSTPONE (LITERAL) , ; IMMEDIATE
首先編譯運(yùn)行時(shí)代碼地址到運(yùn)行過程,然后編譯字面值
6 最終的解釋
接下來會(huì)給出文本解釋和分號(hào)編譯的概括性解釋
Forth系統(tǒng)中的INTERPRET的定義如下
: INTERPRET (--)
BEGIN
BL FIND
IF EXECUTE ?STACK ABORT" Stack empty"
ELSE NUMBER
THEN
AGAIN;
在一個(gè)循環(huán)中授段,試著在輸入字符流中根據(jù)BL切分word,
在字典中查找word届搁,如果定義了就運(yùn)行這個(gè)word窍育,然后檢查是否棧移除。如果查找word失敗漱抓,然后將其看做數(shù)字,存儲(chǔ)到數(shù)字棧stack中
當(dāng)這個(gè)word解析完后重新返回解析下個(gè)word
解釋器就是一個(gè)如此簡單而強(qiáng)力的結(jié)構(gòu)瞬逊,編譯器:
的定義如下
: ]
BEGIN
BL FIND DUP
IF -1 = IF EXECUTE ?STACK ABORT" Stack empty"
ELSE ,
THEN
ELSE DROP (NUMBER) POSTPONE LITERAL
THEN
這里將編譯器定義為]
仪或。而:
調(diào)用]
在一個(gè)循環(huán)中,試著在輸入字符流中根據(jù)BL切分word范删,
然后在字典中查找,如果這個(gè)word定義而且是立即word就立即運(yùn)行旨巷,
如果不是立即word巨缘,則將FIND查找到的地址保存到定義中
如果使用數(shù)字的則將數(shù)字看做字面量存儲(chǔ)
然后重復(fù)這個(gè)循環(huán)若锁。
與解釋器相比懈万,編譯器]
可以看做一個(gè)具有運(yùn)行或者編譯的word的解釋器擴(kuò)展靶病。這正是編譯器可以簡單擴(kuò)展的原因
因此有兩種方式可以用來擴(kuò)展Forth的編譯器,總結(jié)如下
1 通過創(chuàng)建新的定義word(defining words)娄周,來擴(kuò)展數(shù)據(jù)編譯器
2 通過創(chuàng)建新的編譯word(compiling words),來擴(kuò)展過程編譯器