2026年3月16日 星期一

如何設計高低階混用碼指令?

如何設計高低階混用碼指令?


曾慶潭 Ching-Tang Tseng
ilikeforth@gmail.com
Hamilton, New Zealand
16 March 2026


在某些特殊情況,當我們使用 FORTH 系統提供的所有現成指令設計程式時,會希望高低階指令可以任意混合使用,這種特殊編寫程式的功能稱之為混碼程式(Mixing code)技術。程式語言可以有許許多多的古怪技術,但對 FORTH 而言,只要是你描述得出來的技術, FORTH 沒有設計不出來的,而且強調僅由個人私下就可以解決,這是 FORTH 具有延伸性性能使然。不像其他程式語言,特殊功能通常要等原販售公司將其添加於系統,發行新版後才有可能,因為大多數程式語言均屬封閉性,不容使用者隨意修改系統既定功能。本文介紹如何為 FORTH 系統添加混碼程式功能?它就是一個 FORTH 程式語言與其他程式語言比較時,在眾多性能差異上,特別成為鮮明對比的例子。

1 預備知識

初學 FORTH 之人,可能較難看懂這種指令的設計原理,因為初學者通常不會一開始就直接接觸理論性的『FORTH系統結構』與『FORTH指令基本結構』,更進一步的『FORTH執行原理』,則要等學過 FORTH 後,還有興趣深入探討系統時才會去研究它,而欲設計這種指令時,必須先具備這些基本知識,否則不易透徹了解它們的設計方法。另外,因為要混用高低階指令編寫程式,系統當然要具備 FORTH Assembler 的功能,在使用 FORTH 組合語言時,所有的規矩也都得遵循,要有『CPU內有那些暫存器不可亂用?』的限制觀念。上述的預備知識就成了能看懂本文的基本要素了。屬於系統部份的預備知識,你可以從 1987 年 4 月我們出版過的『FORTH期刊』中獲得。組合語言的部份要自行下功夫實作後才能真正體會了解,要先行標出 CPU 內那些暫存器被 FORTH 系統規劃掉了?必須避開不用,或用前搬開內容,用完再合理恢復,所寫的程式執行時才不會出錯。

這些指令不屬於標準指令,在歷來的標準指令表中找不到他們的蹤跡,甚至於最新的 ANSI 標準中也絕口不提,可是它在 FORTH 發展的早期就已經被經常使用了,很久以前,我們在使用這種性能已經多年之後,才在後來新發行的 C 類程式語言系統中見到它的仿照設計。 FORTH 有許多性能領先別人許久,一直到今天依然如此,我們還可以舉出多項性能是一般程式語言所沒有的,這也是為何學會 FORTH 後便愛用 FORTH 的原因。學會設計混碼程式需用指令的設計方法,將更加強對於 FORTH 系統的深入認識,也將終身受用。看懂本文,將來就能輕易的為任何新出現的 FORTH 系統添加此一性能,這是作者為傳揚 FORTH 的最主要目的。因此,特別挑選了一個原本欠缺此一性能的大眾化系統來驗證它,就以 eForth32 做為設計基礎,為其添加此一性能。如果你手頭上使用中的系統已經有此性能,或者只有一半性能,例如:只能在高階定義指令中混入低階指令編寫程式,而於編寫低階定義指令時,還不能混入高階指令。那麼,你就可以根據本文自行添加此性能,自己磨練系統指令的程式設計能力,並與原已有功能比較,將是毫不遜色,因為不同的設計都將殊途同歸,對於程式執行速度或執行效果毫無影響。

如果你尚未準備好上述背景知識,建議你暫時不用細看本文,留供日後參考便可。通常一般商售的 FORTH 系統都提供了此一性能,它們較常用的指令名稱為 : [C 、 C] 、 C: 、 ;C 之類的名稱。前二者成一對,使用在高階定義中轉入低階再轉回高階。後二者成另一對,使用在低階定義中轉入高階再轉回低階。不用讀通本文,你也可以直接使用這些指令,使用手冊的說明文獻也只能做到這個地步。

留供日後參考的建議係作者個人的贈言,四十幾年前,我們在中山科學研究院內的週五研討會上,討論過此項性能。我記得當時藍瑞賢個人獨衷 Zen-Forth 商售系統,其中只提供了這項功能的半套指令,而當時大家公認使用的推廣標準是無版權問題的 F83-FORTH 系統,已有完整的這項功能。為此問題我們另有 Poly-FORTH 系統,也有完整的這項功能,可以比照參考。陳爽的 FORTH 功力當時最好,他帶頭講解的結果,讓與會者此後便有能力自建此功能於任何系統,我則仔細的做了筆記。三十年前我們移民到了紐西蘭,我隨身攜帶來此的書籍、簿本,只有三樣,一本中英對照的『聖經』,一本精裝大字足本的『四書讀本』,另外就是所有的 FORTH 歷年筆記,這些都是精神糧食,好東西就應該留供日後參考,單純只留在腦袋中,老了是會忘記的。我在 eForth32 中重建此功能時,只花了半天時間,而且一次完成,為什麼?靠的就是筆記。

2 使用時機

我們可以舉出許多例子來強調這項功能非常方便,尤其是對一個想玩進 FORTH 系統內部的人更具有吸引力,因為它涉及了低階程式的可以無所不做能力,以及執行速度可以發揮到系統極限的先天優點,另外配上高階程式的現成與複雜功能,二者組合起來,在許多場合就相當方便。因此,若輕易就能擁有的功能,為何不用?它不是標準指令又有甚麼關係? 舉例來說:如果我有一個程式想測系統性能,必須斤斤計較執行速度到系統的基本時脈週期程度。我曾寫過這種程式,就以讀取電腦中的標準時基計數器內容當標準,要讀得又快又穩又準,當然得用低階組合語言來寫程式,想像中幾乎是一瞬間就得到了參考時基,而且只用幾個低階指令,自己耗了幾個時脈數很容易清算,易於隨後扣除。接下來要處理計算與顯示時,若還只能用低階程式,那就非常痛苦了,這項高低階可以混用的功能就很有用處,讓問題極容易解決。

另外,我們在設計中斷處理程式最後,免不了要用低階程式來安放執行位址(Entry point)於中斷執行向量位置,中斷瞬間想以低階最快速度做一件事,隨後想做的事通常就很複雜,不用高階就不方便,做到關鍵處還有可能又要求用低階設計比較有效,如此又高又低的翻來覆去,這項功能更能提供方便。

還有一些平日設計的高階程式,在某些過程中想穿透系統去做一些沒有指令可用的工作,例如:像除錯工具指令一樣可以取得當時 FORTH 系統高階定義指令的程式計數器(Program counter) 內容,高階指令設計中便可混入幾個低階指令來完成。等同於 FORTH 系統高階指令程式計數器的專有名詞,在 FORTH 領域,則稱之為指令指標暫存器(Interpreter Pointer or Instruction Pointer,IP ),系統一般均不提供操作它的指令,古時候的系統則有此 IP 指令可用,它會直接取得系統執行當時的 IP 內容,有幾個結構化跳躍指令,可以藉助於此指令之存在而容易完成設計,後來有新觀念被發展出來才不用它了。

另外,一個接近終了的程式設計,也許會有強調必須改善執行速度的要求,大家都知道,此時非用低階指令取代設計不可了,如果可修正的指令不想搬離原設計位置,那麼,這項功能理所當然的可以提供服務。

還有無數的例子可以舉出,那怕僅只是玩一玩,當基本教材訓練學生,都是有用的,越用 FORTH 設計程式,就越能發現那些場合?可以讓這項功能盡量發揮。我常用,可是很不喜歡用這種程式來當作寫 FORTH 文章的題材,因為要用組合語言,文章只能在限用時間內有效,我擅長用卻不擅長描述這種程式,痛苦之至,最好不寫。很不幸本文的程式設計就不可或免的得用到一些組合語言,我只好將就一點,勉為其難的在該部份進行簡單說明。

3 基本原理

在解說問題時,必須將問題簡化才方便解釋,我們就以這個方式開始。 FORTH 系統中,強調所有的東西都盡可能為統一的格式,因此,無論是高、低階指令,或變數、常數、或字彙,它們的基本結構都是一致的,至少有名稱欄、連結欄,解碼欄、參數欄,某些系統還多了觀察欄或其他欄,我們暫時別管它們的存在與否,只考慮基本上標準的前四欄。

無論你如何寫程式,能被 FORTH 系統接受的最後結果,其格式仍必須符合標準結構的要求,否則不能被正常的執行。因此,我們現在探討的主題,也不能脫離這個範疇。也就是說,高階定義指令中混用低階的指令,或低階指令中混用了高階指令,這個基本結構規矩仍得遵循,不可以在基本結構中另出一基本結構,我們想解說的問題因此被簡化了一半。

四欄的結構有時又被分成兩個部份,一是頭部,也就是名稱欄與連結欄,另一個就是體部,也就是解碼欄與參數欄。分成兩部份,有時有利,有時有害,利害分析這部份,超出本文探討範圍,擱置不予置評,有許多系統都這樣做, eForth32 、Win32Forth 都是, F83 則不是。高階定義指令中使用低階,其中的低階既然不能也是一個原來的標準結構樣子,那麼,必須是怎樣的結構才能被系統接受呢?根據『FORTH執行原理』來考慮,如果我們能夠去除這個下屬於高階指令的低階指令其頭部,系統的執行機制就可以符合正常的 FORTH 系統執行原理。換句話說,如果我們將這樣的程式設計,搞成包括進來的低階指令沒了頭部,只有體部,那麼整個系統的執行機理就能維持而未被破壞。反之,低階指令中使用了高階,其理亦然,只能留住下屬者的體部,而不能有其頭部。根據這樣的原則,我們就能按照FORTH的執行原理,在邊界位置設計程式,來切換系統執行高階或低階指令時的相關程式計數器內容,以達到目的。

所謂的程式計數器,我們暫時可以將情況比擬想像成, Forth 系統中高階定義指令的程式計數器是 IP 。而低階定義指令的程式計數器是 W ,也就是 Forth 系統中所謂的工作暫存器(Current Word Pointer or Working Register)。一般 CPU 所擁有的程式計數器則仍然稱之為 PC ,如果還想再延伸推理到 FORTH 系統中是否還有其他類似意義的程式計數器?那麼我們也可以說負責多工任務(Multi-task)的另一個指標,使用者系統變數指標暫存器 UP(User Pointer),可以算是多工(Tasks)運轉時的程式計數器,接受這一段描述,很可以加強你了解所謂『FORTH虛電腦』這個專有名詞的意義。

如果你很能融會貫通新學的東西,也很有創意,用心想一想,有一天也許能在 FORTH 領域創作出另一個程式計數器,例如:一種中階定義指令的指標暫存器,名叫 MP(Middle Word Pointer ),或者是比 UP 更高的超級程式計數器,用它供作多人使用時、許多系統相連時、甚或全世界網域互通時的指標暫存器之用,它名叫 Super-P ,因為管得太大了,所以名字比較長。讀者千萬不要認為這些推理不切實際,它蘊涵了 FORTH 一貫的哲理。

將指令結構分成兩部份的系統,常強調最終的應用程式被執行時只有體部才被用到,頭部可以說是多餘的,因此分成兩部份放置後,最後可以一次就丟光系統所有的頭部,只留體部,應用程式仍然照舊執行,省了記憶體,還讓有心人無法以逆行追蹤的方式還原源程式的結構。混用碼的程式技術也就是根據這樣的原則,來將其所下屬的高低不平等指令埋進系統的,我們須要的指令就根據此理而設計,這一部份簡化了剩下來另外一半的問題。

4 程式設計

下列是一套設計完成後經過測試的指令組:


 

ONLY FORTH ALSO ASSEMBLER ALSO DEFINITIONS
: [C HERE 2+ , ASSEMBLER [COMPILE] [ ; IMMEDIATE
: C] [ ASSEMBLER ] HERE SPLIT DROP 6 + # SI MOV NEXT FORTH ] ;
CODE DOCOLON
      BP SP XCHG
      DS PUSH
      SI PUSH
      BP SP XCHG
      SI POP
      NEXT 
      END-CODE
CODE (;C)
      BP SP XCHG
      SI POP
      DS POP
      BP SP XCHG
      NEXT 
      END-CODE
: C: [’] DOCOLON CALL, FORTH ] ;
: ;C [ ASSEMBLER ] COMPILE (;C) ASSEMBLER [COMPILE] [ ; IMMEDIATE
FORTH DEFINITIONS



事實上,這個程式可以精簡到只有高階定義的四列程式,上列兩個低階定義的指令 : DOCOLON 與 (;C) 根本就是從原 eForth32 系統中抄來的。我不喜講解組合語言的程式,此處係為了方便而借來使用而已。這個 DOCOLON 在 eForth32 系統源程式中被命名為 doList ,而 (;C) 為 semi ,但系統經蛻變程式(Meta-compiler)長成後,將名稱埋掉了,使用者見不到指令名,因此也就無從直接叫用,指令根本不長,我們就重新以低階指令方式定義一遍又有何妨?也因此讓上列程式多了幾列,而實質重點確實只須四列。

隨便寫兩個測試程式試驗指令功能如下:


 

:  DOUBLE ( n - - )         \  高階混入低階舉例
   DUP                      \ 以高階指令設計,模擬慢速輸入數據
   [C   BX  AX  MOV         \ 以低階指令設計,模擬快速處理數據
   AX  AX  ADD    
   AX  BX  MOV   C]
   CR  .                    \ 恢復方便但慢速的顯示處理
   .” is the double of ”  .  ;

CODE LMH ( - - )            \ 低階混入高階舉例            
     DX  PUSH               \ 以低階指令設計,模擬高速輸入數據
     BX  PUSH
     5  #  BX  MOV
     0  #  DX  MOV
     C:                     \ 轉入高階指令環境
     CR                     \ 以高階指令設計,模擬方便但慢速的數據處理
     .”  The double of 5 is ”  
     DUP  +  . 
     ;C                     \ 恢復低階指令環境
     NEXT	
     END-CODE



它們均執行無誤,可是我得提醒你,在你並不清楚 eForth32 系統中組合語言的使用限制前,千萬不要隨便將一般組合語言的用法,直接設計程式來測試這一組指令,因為系統將 CPU 內的一些暫存器分配給 eForth32 虛電腦使用了,不得破壞,換句話說,你得遵循 FORTH Assembler 的規矩設計低階部份的程式才行。

5 程式說明

前一節中的四列高階設計指令,就是本文的主要重點,不詳加說明誠難理解。現在我們才開始正式的逐個說明程式設計的內容。首先是由高階中進入低階的第一個指令 [C ,為說明方便,它的定義重抄重排如下:


 

: [C   HERE   2+   ,
  ASSEMBLER  
  [COMPILE]   [   ;   IMMEDIATE 
  


我們希望這個『 [C 』指令執行之後,可以開始自由的書寫 FORTH Assembler 程式。說明前可以先行想像,如果不作此額外要求,而是正常高階定義的指令,系統編譯到此處時,應該要編進系統另一個高階指令,或一個低階指令解碼欄所在之所謂的執行位址,現在有此額外要求了,因此必須編進系統一個低階指令格式所須的解碼欄執行位址,就如同前述第 3 節基本原理中已經提到,此後插進來的低階指令,必須是一種沒有頭部,只有體部的格式,因此,我們必須為系統在此處先造好一個低階指令格式的解碼欄,然後接著的內容形同一個低階指令的參數欄,才能自由的書寫 FORTH Assembler 。因此,第一列程式就是這個參數欄。如果你對傳統組合語言很熟,一定看得懂組合語言程式中出現了『 * + 2 』時所代表的意義,它也是以前傳統 FORTH 系統(例如: F83 )源程式中,低階指令解碼欄內所設計的內容,它用來告訴實際上的電腦 CPU ,去執行後續 2 個 bytes 偏移量的組合語言指令。在 FORTH 領域的意義,則是指揮低階程式計數器 W 指到此處,以便將低階指令轉交給真實電腦的程式計數器 PC 執行程式, W 在 PC 處理完後續所有低階指令前則不再變化。因此,『 HERE 2+ , 』 就是安放了一個低階格式所須要的解碼欄,至此明焉!

看似簡單的程式仍有另一個變化,由於 eFroth32 是一個直接線串碼(DTC)式的系統,它將低階指令程式計數器 W 的操作機理,設計成指令的解碼欄中,直接就得開始安放組合語言式的程式,因此,此處可以用『 HERE 2+ , 』達到目的。相對於此的另一種系統,稱之為間接線串碼式的系統,如 F83 ,其低階指令解碼欄中放的就是 * + 2 ,我們反而要設計使用兩次同套指令,也就是『 HERE 2+ , HERE 2+ , 』才能達到目的,係因為其系統中 W 的操作機理有所不同的關係,才必須這樣設計。

接下來的部份就很容易看懂了,可以開始使用組合語言寫程式前,當然要宣告系統找字最優先秩序轉到 ASSEMBLER 字彙之下。然後,要特別注意了,在 FORTH 系統觀念中, FORTH 的組合語言指令被編譯時,是以被執行的方式編寫機器碼入記憶體,而非以編譯狀態來編譯。因此,這個程式中要設計許多次狀態的切換,第三列程式中的『 [COMPILE] [ 』就是第一個。『 [C 』當然不能在高階程式格式,冒號定義的外面使用,所以宣告 IMMEDIATE 。

第二個由低階狀況返回高階狀況的指令 C] 重抄重新排列如下:


 
:  C]  [  ASSEMBLER  ]
   HERE  SPLIT  DROP  6  +  #  SI  MOV
   NEXT  FORTH  ]  ;


第一列程式要求系統進入執譯狀態,宣告找字最優先秩序轉到 ASSEMBLER 字彙之下,然後立刻恢復成編譯狀態,這一列是僅為這個 C] 指令自身設計須要而設計的,因為第二列程式就要開始使用組合語言寫程式了。請注意,暫時摒除程式說明不談,轉看另一個狀況,這裡已經出現了高階內混用低階的情況,有一個 MOV 組合語言用指令,但是它卻出現在系統的編譯狀態下,系統此時不是將組合語言指令的機器碼編譯入記憶體,而是將它編譯成 [C 指令的執行內容,此後使用 [C 指令時,系統才會去執行 [C 指令的內容,包括這個 MOV 。正式的高階內用低階的情況不能像此處的用法,否則,每次都得在這個切換位置擺好這三列程式,整個系統也才能維持正常運作,把三列程式縮減成一個 [C 指令是設計它的目的。

回到此指令的程式說明,我們必須先行解釋第三列程式。這個指令的主要工作,是在用完低階設計程式後,要設法回到高階狀態。前一個 [C 指令造好了體部上方的解碼欄,這一個 C] 指令也要造好一個標準低階指令格式參數欄下方最後的尾綴結構,它們是 NEXT 與 END-CODE ,然後才能回到 FORTH 字彙及高階定義指令所須的編譯狀態,第三列程式即完成這項工作。

回頭看第二列程式所為何在?由低階回到高階狀態時,我們必須先調整好高階指令程式計數器 IP 的內容才能回去, CPU 中的 SI 被指定當 IP 使用,必須讓它指到用完低階,接著出現在 C] 後面那一個指令所在的記憶體位址,如此回到高階狀態時,系統才不會出差錯。這個位址隨著系統的不同,要經過核算才能決定, eForth32 是與當時系統的 HERE 相差了 6 個位元組的記憶體偏移量,它指的是各個使用到 C] 的處所,編譯 HERE 前到編譯完 NEXT 後之位址偏移量為 6 ,如果不能確定此量,可以使用 DUMP 指令看編譯後結果來加以確定。

eForth32 中的 NEXT 內容以普通組合語言表示如下:

LODS WORD \ 1 byte
JMP AX \ 2 bytes

而第二列程式中的 HERE SPLIT DROP 6 + # SI MOV 一長串 FORTH 程式,最後會被編譯成單一個低階指令,可用普通組合語言表示如下:

MOV SI, # value \ 3 bytes

這個總共為 6 的偏移量就是這樣核算出來的。 F83 系 統只要設計成 HERE 6 + 便可得到 value 之值。 eForth32 系統在執行程式時,使用 32 位元的執行位址,因此使用 HERE 後會得到 32 位元值,但是此系統在編譯程式的執行碼時,仍然只用 16 位元的執行位址,如此才能放進 16 位元的 CPU 之 SI 內,因此,我們使用了 HERE SPLIT DROP 6 + 來求得上列 value 之值,完成這個指令的設計。

如果你讀完了上述兩個指令的程式說明完全沒有問題,那麼該恭喜你,接下來的兩個指令的程式說明可以直接了解了。我們一次重抄重排兩個指令的程式設計內容如下:



:  C:   [’]  DOCOLON  CALL,
        FORTH  ]  ;
:  ;C   [  ASSEMBLER  ]
        COMPILE  (;C)
        ASSEMBLER  [COMPILER]  [  ;  IMMEDIATE


設計低階指令程式要轉入可用高階指令狀況前,執行 C: ,此指令首先就安排好去執行一個高階指令該有的體部解碼欄,由第一列程式『 [’] DOCOLON CALL, 』完成,高階定義指令的程式環境,需宣告成第一優先找字的字彙為 FORTH ,且系統須為編譯狀態,由第二列程式『 FORTH ] 』完成,此後在這裡使用任何高階定義方式設計程式就沒問題了。

用完了高階定義格式,一定得再回到低階定義格式的環境去,否則系統就亂了,因此執行過 C: ,就一定得再執行 ;C ,讓系統恢復。這個指令只做了類似前述 C] 指令的工作,將一個編完高階指令後該有的尾綴結構要求 (;C) ,也就是 eForth32 中的半冒號指令『 ; 』之核心結構 semi 編進系統。同時,再次強調,別忘了! FORTH 中的 Assembler 被編譯時是以被執行的方式編寫機器碼入系統,而非以編譯狀態來編譯,這就是最後一列程式的說明。

2026年3月1日 星期日

突破結構化設計測試記錄

突破結構化設計測試記錄


曾慶潭 Ching-Tang Tseng
ilikeforth@gmail.com
Hamilton, New Zealand
2 March 2026



本文係 eForth32 系統中的測試記錄,原稿創作日期為 20111010 ,當時係為了紀念中華民國一百年十月十日而寫。

這是一個反其道而行的論題,程式設計觀念的演變是由原本可以非結構化的規格,演進到現代的強調要結構化,為了防止程式被執行期間因特例而出問題,且易於與人溝通,我也同意設計程式時應該結構化。然而,作為一個 FORTH 自由程式語言的使用者,根本不喜歡別人來干涉我的程式結不結構化?非結構化術語的存在意味著它應有存在的權力,這個自由也不應該被澈底剝奪。我個人的觀點則是:設計程式時應該盡量達到結構化的要求,但在萬一想直接引用前人設計出來的好程式,卻遇到了非結構化的情況,自己又不想大張旗鼓的重改其結構,則應該仍讓我擁有非結構化的權力。

所謂『結構化』,指的是程式執行流程若以繪線來表示,一些迴路或條件分支會形成封閉的迴圈,這些迴圈之線互不相交者就稱之為『結構化』,會相交者就是『非結構化』。舉例而言,早期的 FORTRAN、BASIC ..... 等程式語言,古今所有的 ASSEMBLY 程式語言,都是非結構化程式語言,近代的 BASIC、C++、FORTH ..... 則是結構化程式語言。我們可以這樣認為,所有的程式語言,原本都可由 ASSEMBLY 發展出來,包括其自身亦然。既然 ASSEMBLY 原本即有非結構化之本質,換句話說,程式語言就是有非結構化的天性,是人為的安排使之只能以結構化規格使用的,通常均因系統設計者在編譯程式(Compiler)中加了限制所致,否則所寫的程式無法通過編譯。

如果我們清楚了前述這些情況,當然就很容易了解,一般程式語言一旦有結構化要求之後,就很難再以不結構化方式設計程式了。 FORTH 不然!這也是一項它與一般程式語言有所不同的地方。通常一個市售的標準 FORTH 系統,編譯程式內也設計了結構化檢查的功能,但 FORTH 系統沒有使用者不得操控編譯程式或執譯程式(Interpreter)的嚴格限制,只要你有本事,你就可以進行隨心所欲的彈性運用,重點則在不要破壞了系統或毀了自己的設計。 FORTH 給你這種權力,但後果自行負責,是真正名符其實的『自由程式語言』。

許多年前,我在使用 FORTH 設計數值分析程式時,曾想直接引用一個 Gauss 法求解多元聯立方程式的 FORTRAN 程式,就遭遇到非結構化的問題,當時不想修改程式來適應系統,卻想操控 FORTH 系統來磨練本領,因此將問題提交到中山科學研究院舉辦的週五研討會上討論,陳爽提供了解答,他不愧為 FORTH 系統的個中好手,詳細的說明系統性能,並設計出解決方法,對此後的所有 FORTH 系統是否永久有效? 不能保證,特此聲明。此段記述納入這篇短文,目的在除了不該掠人之美外,也期盼在 FORTH 發展逐漸式微的今天,能與大家分享這個早年不易散播的技術資料。

在正常情況下,這種破壞結構化的程式設計是不被許可的,因為程式無法適應編譯程式,一般 FORTH 系統遇到此一情況,通常會顯示錯誤訊息,且無法通過編譯。我最近均努力於發展 eForth32 系統的應用程式,在一次偶然的試用中,發現這種程式可以通過此系統的編譯,但當然無法被正確執行,原因是 eForth32 非商售的標準系統,它可以不對出此錯事負責,系統為了精簡設計,在編譯結構化分支指令時不設計彼此配對的識別碼,這也是此系統跑得很快的原因之一。

無論 FORTH 系統結構為何?如果瞭解 FORTH 系統在編譯時的一項重點,這個破壞結構化以迎合自己要求的問題就可以解決,此一重點就是──『FORTH在編譯時不用回返堆疊來傳遞參數,反而使用數據堆疊來傳遞參數』。 FORTH 系統在編譯到結構化指令時,通常由於該類指令必須成對使用的要求,會在數據堆疊上留下兩個數值。一是成對要求的識別碼,例如: IF 配 THEN 有一代表碼, DO 配 LOOP 是另一代表碼。另一數值則為結構分支所需的位址值,以便分支指令可以據以編譯入系統。必須特別強調的是, eForth32 系統則簡化成只有一個位址值。

根據這個特點,我們就可以指揮系統但不破壞系統來完成我們的要求,可以形成迴路的指令不僅只是一兩個,我們不宜一一舉例來長篇討論,讀者應舉一能反三,自行活用,須要時仔細拜讀系統結構化指令的詳細內容,也就不可或免。本文僅舉一簡單例子,並設計程式測試,以驗證無誤,提供讀者日後引用時參考。設計驗證的方法也是另類技術,在正式引用前所應先行完成的工作。

例如:我們希望一個 IF ..... THEN 的結構能與 BEGIN ..... UNTIL 結構相跨,還能讓程式正常的被執行。也就是希望程式寫成形同下列的格式,照樣可以被執行:


 

: NON-STRUCTURE
  Statement-1
  Flag-for-IF
     IF     Statement-2
            BEGIN   Statement-3
     THEN   Statement-4
            Flag-for-UNTIL
            UNTIL   Statement-5  ;
 

分析過指令詳細內容後,我們可以發現,兩組結構化分支指令都各有一相關的位址值, IF 可能要跳往 THEN , UNTIL 可能要回跳至 BEGIN 。系統在編譯到 IF 時會留下一個當時系統的位址在數據堆疊上,等到編譯 THEN 時,再將當時系統的位址回填入此堆疊上留下的位址內,完成一組結構化配對指令的要求。同理,系統編譯到 BEGIN 時,也會留下一個系統當時的位址在數據堆疊上,等到編譯 UNTIL 時,系統會安排一個條件分支指令,此留在數據堆疊上的位址,便會被填入此指令之後,以供須要跳躍分支時前往。如果我們不對留在數據堆疊上的兩個位址值進行處理,而仍能通過編譯,那麼編譯的結果,當然會造成跳至錯誤去處的現象, eForth32 系統即為如此。如果我們希望系統仍能編譯出正確的結果,那麼該處理的對象也就是在數據堆疊上所留下的位址值,由上列程式的判讀,我們也可以得知,處理的時機在 BEGIN 與 THEN 之間,至於在 Statement-3 的前面或後面就不太重要了,端視 Statement-3 是否也會在數據堆疊上留下參數而定,因此較妥善的位置,應該是就緊接在 BEGIN 位置之後,處理的方法為在程式的這個位置插入一小段程式,宣告要求系統由編譯狀態轉入執譯狀態,然後立刻執行翻轉數據堆疊頂部當時被留置下來的兩個數值,末了再宣告系統恢復原有的編譯狀態,繼續編譯,這樣就可以人為的方式騙過系統,好像不曾發生過事情一般,完成正常編譯,而且程式可以執行。所添加的三個指令並不會增加系統的編譯量,因為它們在執譯狀態時立即被執行掉了。對 eForth32 系統而言,改寫後的程式如下:


 
: NON-STRUCTURE
  Statement-1
  Flag-for-IF
     IF     Statement-2
            BEGIN  [  SWAP  ]  Statement-3
     THEN   Statement-4
            Flag-for-UNTIL
            UNTIL   Statement-5  ;

至於一般 FORTH 系統,因為多了一組配對識別碼的關係,此處的 SWAP 要改成 2SWAP (不保證都有效!) 。我們就根據原描述程式,設計驗證程式如下:

VARIABLE flag-for-IF -1 flag-for-IF ! VARIABLE flag-for-UNTIL -1 flag-for-UNTIL ! : Statement-1 .” Statement-1 ” ; : Statement-2 .” Statement-2 ” ; : Statement-3 .” Statement-3 ” ; : Statement-4 .” Statement-4 ” ; : Statement-5 .” Statement-5 ” ; : NON-STRUCTURE Statement-1 Flag-for-IF IF Statement-2 BEGIN [ SWAP ] Statement-3 THEN Statement-4 KEY? UNTIL Statement-5 ;

注意!我們用 KEY? 取代了該處原本應該設計的一個可調內容之旗標變數 flag-for-UNTIL ,原因無它, BEGIN ..... UNTIL 如果被編譯完成且被執行, UNTIL 前列的變數就不再有可被調變的機會,它將形成一個永不終止的無限迴路,因此改用 KEY? 來人為控制。這個指令很好用,它是呈現出鍵盤可以被當作一個即時性(Real-time control)控制器使用的專門指令,它具有 FORTH 即時性控制的精神本質。

執行上述編譯完成的程式後,可以根據螢幕上列印出來的文字,判斷你所設計的程式正確與否?此處所舉的例子僅代表本文論述的問題可以被很技巧的解決,至於任意結構化指令的隨意混組,又想要突破其結構化限制,如何解決?則應視情況而定,了解指令執行機制,仿照本文敘述,找到處理對象、處理位置與處理方法並不困難,這不是一件經常要做的事情,不須強記,只須留下本文以供日後需要時參考,另外,則應記得 FORTH 經常與眾不同,它可以變化萬千。

如果仔細深究我設計的 ABC Forth 系統,就具有本文探討的特性,能夠在高階程式的環境,破壞結構化規格。它的應用實例,可以從我在這個網頁中曾經貼出之有關鑑定質數的程式中發現。

例如:程式用來鑑定某一數字是否為質數時,程式的設計方法是核算從 2 開始,直到該數字之開平方以前,該數能否被這些質數除得盡?能除得盡就不是質數,都除不盡就是新產生的質數。這樣的程式,在設計時,我採用了 BASIC 語法中有固定上限指標的 FOR ..... NEXT 迴路完成設計。

為了能在已核算出能被除得盡時不必續算剩餘指標,我就取巧的利用了執行一次 2drop 的技巧,令程式可以從 FOR ..... NEXT 固定迴路中跳出迴圈。這樣做,可以大量節省執行次數與執行時間,就是一種非結構化的用法。

能這樣做的原因,是因為我把 FOR ..... NEXT 迴路的指標 I 之上限與現行數字設計成放在數據堆疊上運作的關係,所以能輕鬆的只用 2drop 指令實現非結構化設計。實際程式可以從本網頁中已貼出的文章中找到,請自行參閱。