突破結構化設計測試記錄
曾慶潭 Ching-Tang Tseng
ilikeforth@gmail.com
Hamilton, New Zealand
2 March 2026
本文係 eForth32 系統中的測試記錄。
這是一個反其道而行的論題,程式設計觀念的演變是由原本可以非結構化的規格,演進到現代的強調要結構化,為了防止程式被執行期間因特例而出問題,且易於與人溝通,我也同意設計程式時應該結構化。然而,作為一個 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 經常與眾不同,它可以變化萬千。
執行上述編譯完成的程式後,可以根據螢幕上列印出來的文字,判斷你所設計的程式正確與否?此處所舉的例子僅代表本文論述的問題可以被很技巧的解決,至於任意結構化指令的隨意混組,又想要突破其結構化限制,如何解決?則應視情況而定,了解指令執行機制,仿照本文敘述,找到處理對象、處理位置與處理方法並不困難,這不是一件經常要做的事情,不須強記,只須留下本文以供日後需要時參考,另外,則應記得 FORTH 經常與眾不同,它可以變化萬千。