2026年7月2日 星期四

創造與實踐特別指令

創造與實踐特別指令


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


創造(CREATE)與實踐(DOES>)兩個 FORTH 標準指令結合在一起使用時,形成一種任何其它程式語言所缺乏的特殊現象。

我將以特殊的手法介紹 CREATE ..... DOES> 指令組。就學習難度分類,這一組算是三級的難度,比二級的 WORD、NUMBER 難一點,比四級的 Multitasking 或 Meta-compiling 觀念簡單一點。

古時候, CREATE 的名稱為 <BUILDS ,而且設計出指令的方法有一點差異, 79 標準以後指令名稱就修改並固定成 CREATE 了。在台灣的 FORTH 同好, 1980 年代使用 AIM65 壓克力封裝的透明電腦、追蹤最原始被公開的 fig-FORTH 系統源程式、閱讀古老的 FORTH 技術文獻 ..... 等狀況時,還有機會見到被使用的 <BUILDS 名稱。我在設計 ABC FORTH 數學計算系統時,也久別重逢了這個指令,所依據的最原始程式,就用 <BUILDS 而不是 CREATE 。

我不跟寫別人的教材,如果沒有自己獨特的一套解說方法,來強化大家學會打算談論的主題,就不該浪費讀者閱讀本文的時間,所以,我會使用特殊的手法寫 FORTH 教材。

我的網文教材中大部份只有淺顯的觀念,但直接切中難了解的相關 FORTH 指令,教會您實際的使用方法。至於一般 FORTH 老手喜歡探討的指令詳細結構,我完全不提。因為,我知道,有一天,您渡過了使用 FORTH 創作程式的初期困難後,會主動的去追蹤每一個指令的細部結構,不需要別人督促。您若不能渡過那種困難,就不會進一步的想深入了解 FORTH 內在系統的結構。因此,教您那些東西,有可能是在浪費時間。現在,我們仍然從外在的使用觀點,經由特殊觀念了解 CREATE ..... DOES> 。

這份教材,調皮的部份,取材自 comp.lang.forth 網頁的一些討論,如果您英文已經看得很順當了,我也介紹您直接去參訪,它不是一個只有我能獨享的 FORTH 科技園地。另外,請不要輕忽那些討論內容,我尊重所有的專家,所以會有『網文』可能比『論文』來得快又來得重要的論點,這是莫之能禦的時代趨勢,老頑固、老學究堅持抵制說『它不正式』也沒有用,頭腦靈活的人早就經常拿這些技術先行實現了許多夢想,寫 FORTH 網文的好手,從來就不在乎公開任何好技術後,會失去可以取得成為博士論文的機會,他們在嘉惠大眾,我因佩服而學習他們。

一般而言,純用電腦專業術語來解釋 CREATE ..... DOES> ,可能還不夠強烈,學習者就不易搞清楚它們的意義,不能記住使用方法,也就難以用來設計特異程式,發揮別種程式語言所沒有的效果。靜宜大學的金城老師主要專長就是電腦,我們曾談過簡化這一組指令的中文解釋,他說:『我宣告一個資料結構,就是 CREATE ,而且決定要怎麼用就怎麼用,就是 DOES> 。』解釋得非常好,大家不妨就這樣記住它們的意義。可是,奇怪的很,他在自己的著作『FORTH 的禪思』第八課中,卻不用這麼言簡意賅的解釋,反而寫了一大堆說明。也許是著作付梓之後,透過交談,才激發出他的簡化潛能,他有空時確實該來與我們多聊聊天,有好的話語,我會幫他記述。

為了讓指令的適當中文名稱能通順的使用於文句中,符合中文文法,以便在傳承技術或進行程式說明時感到方便,我則給予這兩個指令下列中文譯名:

CREATE 創造(資料結構)
DOES> 實踐(後續指令)

此處仍是列而不議(爭議),讀者請笑納便可,我們的重點在只談技術。

這兩個中文名稱又好又偉大,它們的功能確實也是如此,誇張一點形容,單憑這一組指令的功效, FORTH 的性能就足以蓋過其他現行的任何程式語言,寫程式時還能隨心所欲的『創造』和『實踐』,豈有不被稱為最偉大的道理?

為了更清楚解釋它們的性能,我要離題另行介紹一些機械工程學方面的知識,引用這些知識來解釋,就能更加明白這種指令的性能,這樣子學習,有助於強化您的記憶,也增加您對機械工程方面的認識。

學機械工程會見識到許多重型的產業生產用機器,有一些機器被冠以『工作母機』的美好名稱,例如:古時候被稱作車床、銑床、刨床、磨床、鑽床、研床、搪床…者(簡稱:車銑刨磨鑽研搪),均可算是工作母機。其他的機器就不能被納入工作母機的行列,例如:沖床、夾床、壓床、軋床、鍛床、鋸床、油壓機、輸送機、放電加工機、射出成型機…等。這兩者之間有甚麼區別?簡而言之,生產出來的工件成品,可以直接當作其他機器之零件者,才能被稱之為工作母機,例如:車床車製出螺絲,可以給別的機器當零件使用,車床就是。反之,沖床沖製出金屬鍋、碗、瓢、盆,不能給別的機器當零件使用,沖床就不是。『工作母機』就是機器的母級機器,提到她會肅然起敬。

時代在進步,近代的工作母機有了一點變化,例如:機械引擎的活塞缸套,已經發展出了快速、簡便、經濟的生產方法,用搪床來搪缸的機械加工就減少了,這個工作母機也就逐漸式微而少見。又如:以銑刀銑製金屬工件表面的技術,發展得相當進步,製品的品質,已可媲美於傳統龍門刨床刨製出來的金屬表面要求,銑床可以同具刨床的功能,於是刨床也逐漸式微而少見了。現行機械工廠中可以人工操作的工作母機,大約只剩車床、銑床、鑽床三種機器,屬於必備,其他的則可以功成身退了。機器配上了協助自動化的電腦,則改稱 NC 工作母機, NC 是數值控制(Numerical Control)的意思,因為用來操控機器的電腦程式中,大量的使用指揮機器定位或定速所須要的數值,故得其名。機械工程系的學生,學習機械製造這一門科目時,會學到上述這些知識,進行工廠實習時,就會直接接觸這些機器。如果您希望一生都有機會運用機器來創造東西,那麼,大學就該選讀機械工程系,現在則專心談論我們的 CREATE ..... DOES> 兩個 FORTH 指令。

實際用過 FORTH 設計較高級程式後,您才會發現,多工程式(Multi-task)中的 NEWTASK、ALSOTASK 這種指令組,與現在正要討論的創造(CREATE)、實踐(DOES>)此一指令組,都像機械工程學中的工作母機,它們形同為『造指令的指令』,也就是它們都用來設計『零件指令』,再供『應用指令』使用,『應用指令』才能夠方便的被執行,它們就形同是『母級指令』,用到它們當然會肅然起敬。接下來,我們逐步介紹運用這組指令的使用範例。

FORTH 中的標準 VARIABLE 指令,也可以用上述 CREATE ..... DOES>『母級指令』來設計,這是最簡單的這一組指令使用範例,例如按照下列程式逐列操作:

: VARIABLE1 CREATE 0 , DOES> ; ok
VARIABLE1 TEST1 ok
18 TEST1 ! ok
TEST1 @ . 18 ok

這個 VARIABLE1 的功能,就與 FORTH 中的標準變數宣告指令 VARIABLE 完全一樣了。

其中, TEST1 相當於是一個『應用指令』,可以在程式中到處應用,它須要使用 VARIABLE1 這種『零件指令』才能宣告形成,而『母級指令』 CREATE ..... DOES> 則可以完成『零件指令』 VARIABLE1 的設計。

仔細追蹤 VARIABLE1 『零件指令』的設計內容,從創造(CREATE)資料結構開始,系統首先根據使用者隨後使用時,賦予的任意名稱 TEST1 ,在系統中創造了一個變數的資料結構,而且先將資料內容歸 0 ,創造到此結束。此後,每逢 TEST1 變數被應用而執行時,系統就實踐(DOES>)後續指令,此簡例中沒有任何指令,於是實踐(DOES>)就只執行將創造完成的 TEST1 資料結構之參數欄起始位址,放在系統數據堆疊上,僅此一項工作為實踐(DOES>)天生非做不可的實踐內容,實踐到此結束。

以前,有一個馬戲團小丑,在表演模仿走鋼索前,把地板掃一掃,想放置脫下來的外套,可是等他脫下外套後,卻把外套丟到很遠很遠沒掃過的地方去了。小丑的調皮,可以引起觀眾開懷大笑。我們也來學一學小丑,調皮的不按規矩使用這一組母級指令,依序執行下列程式,就能看到它們的效果:

: NAUGHTY1 CREATE DOES> DROP 1 . ; ok
NAUGHTY1 TEST2 ok
TEST2 1 ok

這組母級指令造好的 NAUGHTY1 零件指令,被 TEST2 應用指令使用時, TEST2 根本沒有資料結構的體部,也就是根本沒有內容,應用指令 TEST2 每次被執行時,卻都拓印出 1 ,它天生非做不可的實踐內容,實踐完了就立刻被丟棄不用了,這個程式就像上述的小丑,有一點調皮,卻產生了一點特殊效果。

既然調皮有點看頭,那就進一步調皮一點,看看使用兩組實踐 DOES> 配一組創造 CREATE 的效果,又會如何?依例逐列執行程式,得到了下列結果:

: NAUGHTY2 CREATE DOES> DROP 1 . DOES> DROP 2 . ; ok
NAUGHTY2 TEST3 ok
NAUGHTY2 TEST4 ok
TEST3 1 ok
TEST4 2 ok

您可以跟著調皮下去,看看續用這個 NAUGHTY2 零件指令,多宣告出幾個應用指令,會有甚麼結果?還有更調皮的設計,設計成下列:

: NAUGHTY3 CREATE BEGIN DOES> DROP 1 . DOES> DROP 2 . AGAIN ;

注意! BEGIN ..... AGAIN 是一個無限循環,這樣子設計程式,執行起來竟然可以不毀系統,而且您還可以照常在 ABC FORTH 系統中繼續發展程式,請您自己試一試這些現象,或者想辦法比上述所有程式更加調皮,看看能有甚麼結果?

我不聰敏,拿這樣的範例來應用,仍然可以設計一個上課點名遊戲程式,只是學生都是虛擬的存在電腦中。

\ 母級指令 CREATE ..... DOES> 測試程式
\ 作者:曾慶潭 2010-02-16

: 男學生 CREATE DOES> DROP ." 有!我是男的。" ;
: 女學生 CREATE DOES> DROP ." 有!我是女的。" ;
: 兩性生 CREATE BEGIN
DOES> DROP ." 有!我是男的。"
DOES> DROP ." 有!我是女的。"
AGAIN ;

\ (( 使用範例:上課點名遊戲程式
男學生 湯姆
女學生 瑪莉
兩性生 喬治
\ ))

您可以實際去執行一下,分別鍵入學生的名字後,看看結果。『喬治』則要多點幾次名,他(她)真的會忽男忽女,好像有一個名叫『喬治男孩』的明星就是這樣,他(她)們有公開存在於現今世界的權力,不要以異樣的眼光看待他(她)們。我們的 FORTH 真正是一個既古老又先進的程式語言,它憑著創造(CREATE)與實踐(DOES>), 在 58 年前(1968),就把今天這個時代的爭議性問題處理得這麼好。

談起如此的一組 FORTH 傑出指令,實在可以創作出無限的調皮話題。

我所設計的 ABC FORTH 數學計算系統,更是靠著它們才得以完成,系統裡面的所有變數結構都在變化。變數出現在等號『 = 』的左邊或是右邊時,性能就得不同。變數若使用於浮點數的環境,如果還得用到指標,它的指標就只能為整數,有幾個整數還得視情況而定。這麼囉嗦的要求,如果不用調皮的手法,可能很難完成設計。

能用上述模式解釋這一組傑出指令,平生就得多學、多用、多涉獵各方面的知識,頭腦才能有豐富的聯想力來進行描述,讀者可能看似簡單,實則未必,要強調這樣的論點,可以透過一個別人講過的笑話來形容它,您自己體會,同時結束本文。

一個幼年私塾,有一班幼童背頌了一段課文,內容是:
『人坐著吃飯,馬站著吃草。』
過了一會兒,私塾的老先生要幼童起來背頌這篇課文,小華先背,可是站起來就怕老先生用戒尺處罰,於是背出了:
『馬坐著吃草 ..... 』
大家都笑了,老先生有點生氣,用戒尺從輕發落了一下小手心就算了,換小明站起來再背頌,小小的心靈真可憐,同樣的狀況:
『人站著吃飯 ..... 』
老先生再輕輕的打了小明一下小手心,讓他坐下,然後說:
『這麼簡單都不會,現在大家把書本合起來,都跟著我唸。』
『人坐著吃馬 ..... 』

這兩天紐西蘭夜間的氣溫低於 0 度 C ,我準備貼文時確實有點冷。

2026年6月16日 星期二

蛻變編譯

蛻變編譯


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


本文透過簡單的生物學原理,講解 FORTH 系統中,以蛻變編譯(Meta-compile)生長技術,發展出新系統的基本觀念,很適合作為了解並永久記住操作蛻變編譯時的依據。

日本的心理學家新崎盛紀,在他的著作『直覺力』中,以機能定義『創造』為:『把性質相異的素材,重新排列組合成前所未有的新事物。』,言簡意賅,本身就創造了『創造』。

創作則是創造作品,會有物品產出,它代表著成就,令創作者愉快。好的作品還能用來造福人群,美化世界,這樣的創作就值得鼓勵,而且越多越好。我喜歡創作,也喜歡別人的創作,更喜歡見到專家那麼簡明扼要的描述『創造』。

年輕時,自覺見聞、知識、經驗與觀念都有所欠缺,創作嫌早;年邁後才想要創作,可能就會嫌晚而力不從心;現在恰似正逢其時,該使出渾身解數實現自己的喜好了。已經有許多年的時間,我似乎同時進行了兩項創作,一是文章,另一則是電腦程式系統,兩者皆我所好,總想不要顧此失彼,二者可以兼得,這個網頁貼文的內容因此就由兩者揉合而成。我很愛惜自己的作品,也很希望能與世界互動,並且隨時反省、把握住作品要有正面的效果,讓讀者受益。

回顧 ABC FORTH 數學計算系統,真可算是符合前述『創造』的定義,那裡面實在沒有太多純屬於我個人的作品,在著作書本以便留下設計技術時,我寫過:『我是站在巨人的肩膀上往前看』,表示我取用了大量性質相異的素材,素材早就已經存在,重新排列組合出『ABC FORTH數學計算系統』,它確實是前所未有的新事物,這裡面甚至於擁有許多我自己都還不曾經歷過的使用性能。

創造或創作並不困難,它不是要你從事於『無中生有』的事情,而是要你從事於『有中生好』的工作,重點在構思如何重新組織與安排出別人辦不到的結果。這樣的解說提醒大家,為了人類的福祉,要多行有意義的創造與創作。

『生命』是人人愛惜的,才會有人喜歡養寵物、栽盆景,因為這些東西都有生命特徵,『活潑可愛』通常都是我們對這些喜歡對象的主要描述。根據這個道理,我很可以使用一種很有創意的方式,敘述 FORTH 為什麼能被大家喜愛?因為,我發現 FORTH 相對於其他程式語言,具有別人所沒有的『生命特徵』。

要解釋這樣的觀點,必須引用生物學上對『生命』所下的定義。 FORTH 扯上生物學並不離譜,但不是『電腦』與『人工智慧』上的那種關係,我有我的特殊看法,會有這種創意看法是其來有自。

我從台灣桃園武陵中學高中畢業,武陵中學是一所不差的學校,當年高中一年級時,總共有十個班,一班平均約四十幾個學生,蔡惠美老師教我們生物學,我還記得她把期末考的試題出得非常難拿高分,全用複選題,選完還得寫理由,寫錯了每題都會倒扣。

高中二年級起,全校學生要開始重新分班,根據未來打算選讀大學科系的志向,分出不同的班級,分成自然、文史、農醫、法商四種類別,各自加強學習各別不同的科目,面對大學入學考試,進行不同的選擇。

高一考完期末考後,我才得知,全校只有兩個學生,期末考生物學的分數是及格的,滿分是一百分,六十分及格,我考了七十六分,另外一個後來成為醫生的同學,考了六十一分。蔡老師特別問我是否想唸當年分類為丙組的農醫類組,我恭敬的回答想唸理工科學類別,屬於甲組的自然類組,答案當然令老師失望,不過我確實熱愛所有的自然科學,包括博物學與生物學,至今我仍有能力自己閱讀這方面的書籍。有過這一段歷史,所以我會有 FORTH 與生物學方面的獨特看法。

所有生物學基本教材第一章都討論有關『生命』的現象,你若不熟,可以找一本淺顯的生物學教科書來回憶一下,它很簡單,只是說明,並不難懂,我自己是特別了一點,會牢記它,還會推敲它。一般而言,生物學家都認為生物可以歸納出四種生命特徵,也就是新陳代謝(metabolism)、生長(growing)、感應(sensitivity)與繁殖(re-production)四種生命特徵,我小女兒提醒過我,所有生物都該有另一項『死亡』(mortality)的特別現象,我也覺得合理,應該納入,生物學書本沒這樣寫。

我們可以根據上述的生命特徵來探討 FORTH ,你就可以發現人人喜歡有生命的東西,如果你了解 FORTH 也有生命特徵,它一旦被了解後,自然就會被喜歡,也就不足為奇了。

一般的程式語言系統,還不曾有過被設計成像 FORTH 這樣的結構,也許將來會有,但此前沒有。舉凡市售的程式語言系統,例如:ALGOL、COBOL、BASIC、FORTRAN、PASCAL、ADA、C、LISP、PROLOG、ASSEMBLY ..... 等等我曾接觸過的程式語言,結構都不同於 FORTH ,最大的不同是這些系統整體結構絕對是死的,不管它是以那一種編譯器(Compiler)或執譯器(Interpreter)設計而成的程式語言,它們的編譯器或執譯器是絕對不允許使用者亂動的,誇張一點說,是不容你動它的一絲、一毫、一根汗毛,那怕是僅改一個碼(Code)都不行,所以我很誇張的形容它們都是死的,一點都不為過。它們從大公司出廠的那一天開始,就被製成像標本一樣的死東西,可能很好看,使用者卻絕對不能從它感受到任何一項上述的生命特徵,或要它活過來。請問你以前使用過的任何上述程式語言系統,它的屍體如果仍然存在,它有任何一丁點的改變嗎?沒有,它確實是死在那裡,或者是逝去得無影無蹤了。

回頭來看 FORTH ,它那一點像有生命特徵的樣子?有的,所有的生命特徵它還全都有呢!只差它不會死亡,否則它就令人感到可怕而不敢喜歡了。

生物的新陳代謝,講的是生物攝取食物,獲得養份與能量,過程中有些許物質交換,生物處理源源不斷的輸入,進行活動並排泄廢物。

西藏有一種傳說中的動物被雕刻成飾物,名叫皮丘,傳言牠吃東西,只進不出,愛打麻將的人如果將此飾物帶在口袋裡,象徵一定贏錢,而且只進不出,據說萬一被對手發現,會被痛揙一頓,然後輸贏全都不算。皮丘只是傳說中的動物,不是真的,牠不會新陳代謝,所以不能被介定為生物。

FORTH 就新陳代謝此一特點而論,它是擁有這項特徵, FORTH 一旦在電腦上運轉起來,它的運轉方式就一直模仿著生物的新陳代謝特徵,處理源源不斷的輸入,如果輸入是指令,它會從自己的本體內比對出來,然後就依指令的內容,將此輸入以執行方式消化掉,就好像是取得了外界的能量,進行了一次相當健康的活動,最後愉愉快快的通知使用者一個『ok』,顯示在螢幕上。如果輸入是數字,它就將數字轉換成它自己消化後可以取用的型式,暫時存放在系統內的數字堆疊上,留供後用,這也是另一次系統的活動,有些許的物質交換,我們還可以強調這像是它的系統內部起了變化。如果輸入是一堆垃圾,它也認得,它就拒絕接受,然後不客氣的問你,這是甚麼?自從你會操作 FORTH 系統之後,你不覺得你因 FORTH 會這樣子進行新陳代謝而感到喜歡它嗎?

關於生命特徵的第二個生長現象,對 FORTH 而言更是特別。前已述及,別種程式語言系統的內部是不容更動的, FORTH 不同,它被設計成像生物一樣,隨時可以生長。每當你用冒號定義直接設計 FORTH 指令時, FORTH 的系統就在生長了,除了從冒號 『:』 到半冒號 『;』 之間所定義的程式指令,可以令系統成長外,還有變數『VARIABLE』、常數『CONSTANT』、字彙『VOCABULARY』…等的宣告,以及低階指令從『CODE』到『END-CODE』之間設計的程式指令,也都會令系統成長,而且確確實實與系統紮實的長在一起, FORTH 才能繼續『活潑可愛』的活動下去。另外還有特殊的嵌碼『,』入系統、配置『ALLOT』一段記憶體空間 ..... 等指令,也能令系統成長,由檔案載入程式,長的更快。如果長得不好,還可以使用忘掉『FORGET』指令,將不想要的部份一口氣就排泄掉,某些 FORTH 系統還能使用一個簡單的特殊指令,將現行系統立刻固定下來,Win32Forth系統使用的是指令『FSAVE』。你看, FORTH 怎麼沒有生長特徵?請注意!所有程式都跟系統長在一起, FORTH 只有一個整體式的結構,它會生長,統統長在一起,生物的生長不都也是這種長法嗎?別的程式語言系統,行嗎?

更有甚者, FORTH 的生長特徵是可以修改的,我所創作的『ABC FORTH數學計算系統』,如果不對原始FORTH母系統Win32Forth的核心部份,在生長時進行修改,不可能達到符合要求的性能設計,我只是 FORTH 的老手不是高手,就能辦到此事。我的這一生也搞過其他幾種程式語言系統,但都可以確信,此生是別想在別的系統上動任何念頭了,如果還有來生,那麼,來生再看看吧。

談到生命的第三種特徵──感應現象,是生物得對環境的刺激有所反應,才能稱之為具有感應能力, FORTH 號稱具有立即(Real time)響應能力,指的就是這種能力。操作 FORTH 系統時,你所鍵入(Type in)的每一個字元,都由 FORTH 系統內的執譯器(Interpreter)立刻進行處理,是 FORTH 系統直接對輸入進行反應,是 FORTH 隨時都對刺激它的環境訊息進行感應。要它執行有感應能力的程式時,也可以利用特殊指令『KEY』或『KEY?』直接對鍵盤輸入進行反應。若要設計對電腦周邊的感應程式,當然也比當前任何其它種程式語言系統容易,我們過去幾十年,已經設計過許多這種程式,只有又快又好的感覺。別種程式語言也能用來設計有感應功能的程式,但請注意!是被譯成的程式可以感應,不是別種程式語言系統自身在做感應。

第四種繁殖能力最為特別,一般程式語言系統要想具有繁殖能力,簡直就是天方夜譚、不可思議,賣系統給你的廠商,根本不為系統設計生殖器官,要不然他們就不能繼續賺你的錢了,因為你一定會用首次買到的系統來生出新系統,他們以後怎麼可能再賺到你的錢?微軟的老板就因為這樣而成為有錢的人。作業系統中雖有 COPY 功能,可以讓使用者省下一點還得再買同一系統的錢,但也只能複製程式,絕非繁殖系統。

程式語言系統的生殖器官難道設計不出來嗎?不是的,絕對可以辦到,只是除了 FORTH 以外,別人確實是很難設計出來罷了。因為生殖方法牽扯到生殖的哲理,僅有生殖器官,若沒搞清楚生殖的標準方法,還是生不出新的系統。此前,仍然只有 FORTH 的哲理內包括了生殖哲理,它還沿襲了生物的生殖方式來進行生殖,是唯一具有生殖特徵的程式語言。

生物要能生殖,他的自身首先就得依功能進行高度的分化,然後又要求生命的最基本單元要統一成單純的結構,也就是所謂的單個生物細胞結構都得類似。

想一想你自己的生命,你有高度分化的器官,卻都由細胞組成,細胞雖然不同,其基本結構則都一致,例如:細胞核內有遺傳染色體(Chromosomes)、細胞體內有粒腺體(Mitochondria)、高基氏體(Golgi bodies) ..... 等等,不同器官的不同細胞都必須要有一種類似的結構,如此,生物才能生殖,因為生殖要從單一個胚胎細胞開始。

再想一想 FORTH ,漂亮健全的 FORTH 系統,也有依功能進行高度分化的程式內容,例如:檔案處理、浮點運算、繪圖功能、編輯程式、甚至於等同於生物生殖器的繁殖程式(Meta-compiler)…等等,他們分別被歸類到相關的器官字彙(Vocabulary)內,高度的分化了。但不管各個程式的功能如何,組成它們的最基本單元,也就是基本指令,在 FORTH 中稱之為一個字(Word)者,其結構也都完全統一,只是內容不同。也可視同是最基本單元的常數、變數、字彙 ..... 等,他們的結構也都跟最基本的指令一樣,只有所謂的名稱欄、連結欄、解碼欄、參數欄 ..... 等相同結構。 FORTH 的設計哲理,安排成與生物生存的哲理完全一致, FORTH 就容易擁有生殖特徵。基本細胞結構一致,生物就容易完成生殖;基本指令結構一致, FORTH 就容易也就會生殖。

除此之外, FORTH 的生殖方法,也與生物的生殖方法類似。生物在胚胎生長時,先從單一個胚胎細胞開始,然後進行細胞分裂,在到達某一固定數量前,分裂出來的細胞不做功能分化。我特別請教台北醫學大學的微生物老師商惠芳博士,驗證這種現象,她明確的告訴我,確實如此,生物在繁殖時,細胞分裂到 2 的 5 次方,也就是 32 個以前,是不進行功能性分化的,此後才進行功能性分化。 FORTH 繁殖方法亦然,它也先長出一個不進行功能分化的核心(Kernel)程式,再用這個核心程式去長出各個功能不同的程式,以形成一個健全的新系統。

FORTH 有許多表徴現象,與我們熟悉的生物生命現象如此的類似,絕非偶然,當初發明它的莫查理(Charles H. Moore)先生將其設計成這個樣子,他所想像出來的FORTH 基本哲理,在有意或無意中應該是參考了生命現象而成,這樣形成的可貴哲理,有助於 FORTH 的恆久存在,因為我們熱愛生命,只要你能了解 FORTH ,顯現許多生命特徵的 FORTH ,自然就能為你所愛。

自古以來,我曾接觸過的好幾套 FORTH 系統,它們生殖過程的操作方法也都類似,Win32Forth 系統亦然,新手可以一開始接觸 FORTH 就學生孩子的方法,操作過程也很簡單,耗時並不長,但很可以引起大家的興趣。

在 Win32Forth 系統中,以蛻變編譯器(Meta-compiler)產生新系統的方法:

(1).先由執行中的 Win32Forth 標準系統載入蛻變編譯程式,其程式名稱為 META.F ,由鍵盤鍵入下列指令

FLOAD META.F

系統會自動產生一個核心系統,名稱為『KERNEL應用程式』的可執行檔案。

(2).以 BYE 掉系統的方式,離開現正執行中的 Win32Forth 標準系統,然後去執行前一步驟所新產生出來的 FORTH 可執行檔『KERNEL應用程式』。
此時你已經可以進行一般性的 FORTH 指令操作了,但此核心系統很小,用 WORDS 指令可以看到它所擁有的少數有限指令。

(3).從這個執行中的核心系統載入延伸出整體性能的主控檔案,其程式名稱為 ENTEND.F ,由鍵盤鍵入下列指令

FLOAD EXTEND.F

系統便自行載入所有相關程式,稍等片刻,一個完全像 Win32Forth 的健全新 FORTH 系統,『WIN32FOR應用程式』就會產生出來,另外附帶產生幾個為了應付 XP 作業系統所須要的相關檔案,完成了一次生產。

V6.14版依然使用傳統 FORTH 的蛻變編譯原理來生成系統,也與 V4.2 版的基本原理相同,差別只在更複雜的 V6.14 系統,就有更複雜的 EXTEND.F 內容。

透過生物學的模式,了解 FORTH 蛻變編譯程式的基本原理,是很好的學習方法,學過之後也有助於永久記憶,這是我的經驗。

從上述有別於一般介紹 FORTH 特性的敘述,你可以看出,我以異於常規的方式思考問題,所以也能以異於常規的方式設計系統,『ABC FORTH數學計算系統』就是以這種方式設計出來的。

已經獲得這個系統使用說明的人,用後應該有所體會,這個系統沒能像市售系統那般,提供所有的慣用指令。使用說明的最後一頁告訴大家,設計這個系統的目的並非在重新創作一套程式語言系統,設計它的初衷,只在改善 FORTH 的數學計算應用性能。但是,這個系統絕對可以達到任何數學計算程式要求的程式執行能力,我已舉出大型有用的實例證明了它。

每當我使用這個系統設計程式時,依然難免需要準備好使用說明,隨時參考,才不會用錯指令或函數名稱,這就是使用說明的實際價值。想把所有慣用指令或功能特異的指令都設計出來,我也不是辦不到,但會破壞這個系統的真正價值,我將設計這個系統的眼光看得很遠,試想,五十年後還想讓這個系統容易繼續存在,唯有現在就將其設計成最精簡扼要的程度才有可能。 FORTH 的哲理就是這樣, eFORTH 的精神更是如此, ABC FORTH 也不例外。有 FORTH 先進,建議我將某幾個指令設計成某種格式,將更為理想,我也同意這些構想很好,但我自肘功力絕對不及 Charles H. Moore ,最好暫時別改他的設計,恰如孔子所說的:『爾愛其羊,我愛其禮。』,使用者就請以禮待此系統。

我曾在講演的投影片中列舉了 ABC FORTH 數學計算系統的十大特點:

(1).全面讓不同程度的所有使用者,易於設計數學計算程式。
(2).讓過去幾十年所發展出來的 ALGOL、FORTRAN、BASIC 數學計算程式,易於轉譯改寫進本系統而再度有用。
(3).這個系統易於自行建立,因此可以確保幾十年後這些數學計算程式仍然有用。
(4).改善此前不方便設計數學計算程式的缺點,此後再配合即時性處理數學計算程式的能力,便可以設計出線上立即執行數學計算功能的程式。
(5).讓數學計算程式全面中文化。
(6).讓數學計算程式全面各國語文化。
(7).讓發展出這個系統的技術全面大眾化。
(8).為未來可能出現的新數學體系留下發展典範。
(9).救貧不救懶:對缺乏程式工具與程式技術者有用,對懶得寫程式者不管用。
(10).這是一個永遠免費的公益系統。

在系統公開的初始階段,這十個特點可能要靠我自己身體力行來給予證明,我不怕做這樣的事情,我也樂於從事,因為『ABC FORTH數學計算系統』是我個人的創作,我有恆心與毅力來推廣它的應用。

好的創作是有意義的,在創作中記錄寶貴的經驗就是更有意義的事情,因為對 FORTH 技術的傳承會有幫助。公開好的作品也應該是有目的的,絕對不可以無的放矢,套用一句中華民國到處可見的優良標語:『生命的意義在創造宇宙繼起的生命,生活的目的在增進人類全體的生活。』您是否仔細思考過這個標語的真正意義?這篇文章中談到了生命,『ABC FORTH數學計算系統』是 FORTH 繼起的生命,也談到了目的,這個系統的真正目的就是想增進人類全體的生活。

2026年6月2日 星期二

改變執行秩序的技巧

改變執行秩序的技巧


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


剛開始接觸FORTH程式語言時,大家都對這種程式語言整個的特性感到陌生,因為它的使用習慣與所有其它的程式語言完全不同,系統強調數據傳遞時,全面使用堆疊、大部份指令都採用後算符的格式來設計、程式中竟然可以操控系統的執譯(Interpret)或編譯(Compile)狀態變化……等等,許多特性都只在 FORTH 程式語言中才有,乍看之下是不好用。

但若仔細推敲,卻讓使用者感到其執行結果都是直接了當,方便之至。想看記憶體的內容時,就直接飛取(Fetch, @)來看,或直接傾印(DUMP)來看。想改變數值計算的進制時,就直接將任意的進制值存入系統內名為數基(BASE)的變數。甚至於存或取出大批資料儲存媒體龐大硬碟的內容時,也是實體位置的直接操作。具有電腦對外界直接進行輸出或輸入的指令可用。它的許多長處蓋過了它的許多短處。

事實上,所有 FORTH 的短處,在發明當初,都有它被設計成那樣的理由,這些短處是顧及全面性的問題而建立,不是簡單隨便用一用 FORTH 的人所能體會出來的,一個好手要在設計過大型應用系統之後,才能得到這些感覺。相對的,雖然 FORTH 後來發展出許多新技術,但可以感受得出來,為了解決問題而發展的新技術,通常都只有針對性,而未具有絕對的必要性,歸根結底的研究之後,就能發現幾乎可以不必刻意發展某些新技術,例如:後來才發展出來的局部變數(Local variable)設計技術,即為一例。

某些新發展出來的新技術,就應用層面而言,通常也比較狹隘,難稱能夠顧全大局。例如:我為了改善 FORTH 數學計算的程式設計方法,而在 FORTH 基礎系統上,加建了 ABC FORTH 的新性能,這樣做,純粹只是為了數學計算問題而加建,沒有顧及其它如字串處理類的問題,而且根本不修改原基礎 FORTH 系統的任何功能, FORTH 還是 FORTH 。

新技術與新觀念是需要長時間才能發展累積出來的, FORTH 中令人感到原為短處的性能,都能隨著發展而改善,原為長處的性能,也能因發展而更為提升,本文就討論這方面的問題。

大家剛開始研究 FORTH 程式語言的性能時,曾經歸納出一份新手學習曲線圖,圖中表示:一開始就講解如何以冒號 (:) 定義開始設計程式,並以半冒號 (;) 結束定義單一個新指令的程式設計,五分鐘內就能學得FORTH程式設計的最基本方式。接下來就有一點困難了,典型第二級比較難以學會的指令是處理文、數字輸入的 WORD 與 NUMBER 指令。然後,第三級學習上更艱難的指令就是創造(CREATE)與實踐(DOES>),兩者組合而成的特異功能性程式。第四級難懂的多工觀念。最後,也屬第四級最難完全透徹了解的觀念,就是 FORTH 能自行產生標的(Target)系統的蛻變編譯。


指令執行的先後秩序

大概所有認得 FORTH 程式語言的人,都知道後算符是怎麼一回事了。尤其是使用者想完成兩個簡單數字的計算時,算元(Operands)就得先擺在前面,將它們先放在堆疊上,然後才能執行後算符式的算子(Operator)指令,所得結果,仍然留在堆疊上。指令執行的先後秩序,跟其它程式語言都不一樣,有點煩人。

但是,這些限制,實際上都並不是絕對一成不變,非這樣不可的規矩, FORTH 程式語言,完全允許使用者對任何不滿意的狀況,自行設計改善程式來改變狀況。例如:如果不滿意於 FORTH 系統硬要使用者按照後算符的規矩使用四則運算,也可以立即設計程式進行改善,程式也很簡短,啟動系統後,幾乎可以立即建立這樣的功能。程式簡單的程度,就如下列:


 
FORTH  DEFINITIONS

VOCABULARY IKQ   \ Integer Calculator
VOCABULARY FKQ   \ Floating Calculator

ONLY FORTH ALSO IKQ ALSO DEFINITIONS

: NEXTI BL WORD NUMBER DROP ;
: + NEXTI + ;
: - NEXTI - ;
: * NEXTI * ;
: / NEXTI / ;
: = . ;

ONLY FORTH ALSO FKQ ALSO DEFINITIONS

: NEXTF BL WORD COUNT >FLOAT DROP ;
: + NEXTF F+ ;
: - NEXTF F- ;
: * NEXTF F* ;
: / NEXTF F/ ;
: = G. ;

FORTH DEFINITIONS



將上列程式載入系統後,按傳統規矩執行數字的四則運算,就能得到下列直截了當的計算顯示,最後再以宣告 FORTH ,讓系統回到正常字彙之下的方式,將一切恢復成正常的狀態:

IKQ ok
9732 - 123 = 9609 ok
FKQ ok
6.02E23 / 3E0 = 2.006666667E23 ok
FORTH

這樣的四則運算是標準的中算符而不是後算符了,原來的 FORTH 系統卻完好如初,未經破壞。這樣的性能, FORTH 發明當初就已經存在了,因為上述程式中所有用到的指令,在 FORTH 程式語言發明時就已存在。此處藉著這樣的用法,來解釋第二級難度的標準指令 WORD 與 NUMBER 。

首先,必須聲明的是,想要全面實現中算符書寫方式的要求,不能僅只依靠上列的程式來完成。因為,還有許多系統性的問題必須考慮,例如:上述的設計並不顧及先乘除後加減的問題,也不允許任意使用小括弧,也不考慮任何數學函數,也不能用來處理非數字之變數的計算……等等。所以,這樣的性能是具有非常之針對性的,不是一切。

那麼,又是甚麼性能?讓我們能夠迅捷的實現這種針對性的要求。簡而言之,就是透過適合用來改變指令執行秩序的 WORD 指令,予以完成的。

在 FORTH 系統的文、數字輸入處理中,隱性的存在了三個觀念上的緩衝區(Buffers),第一個是終端機輸入緩衝區(Terminal input buffer, TIB),處理直接由鍵盤鍵入的文字,可以直接執行 TIB 指令顯示這個位址。第二個是現行輸入文字緩衝區(Word’s buffer),它就在系統字典長到此處(HERE)的位址處,或稱之為將要建立連結進系統的下一個可用新位址,可以直接執行 HERE 顯示這個位址。第三個是專門用來處理字串,但可隨系統增長而浮動存在的字串暫時儲存緩衝區(Text’s buffer),這個緩衝區的位址,可以直接執行 PAD 指令顯示出來。

古時候的 FORTH 系統,還使用專門用來將輸入字串送進 PAD 緩衝區的指令,它就名叫 -TEXT ,後來的系統不再使用而消失掉了。新作業系統太複雜,而令 FORTH 系統不得不在 TIB 緩衝區,將 FORTH 系統與作業系統進行嚴格的區隔,因此, TIB 緩衝區的一般性操作,也改讓使用者在較高層次的其它高階指令內進行。唯獨這個處於 HERE 緩衝區的 WORD 指令,永遠存在,沒有被除去,只不過某些 FORTH 系統在 WORD 指令執行之後,在堆疊上留或不留下 HERE 的位址,有所差別而已。不被除去的原因也很單純,因為輸入的文、數字,可能立刻就要被編納入系統,所以就理所當然的該留在 HERE 所處的位置。將字串送進記憶體的專門指令中,還有一個專門用來送進任意記憶體位址,而不是這三個緩衝區的 EXPECT 指令,它工作原理簡易,只屬於第一級的初級層次,此處不詳加解釋。

WORD 指令在執行前,必須提供一個當作邊際界標的字元,通常,根據 FORTH 程式語言的習慣,我們都取用空格(Blank, BL)當作界標,隨後, WORD 指令會隨著系統被指定的輸入狀況來處理輸入,輸入有可能只是終端機的螢幕,也可能是來自於一個檔案,或其它能夠被指定的輸入方式。他的處理方式,就是以輸入界標字元為邊界,要求系統處理此單一個輸入字串,處理完則留下 HERE 的位址。系統因此就只處理執行 WORD 指令當時的那一個字。

在上述程式中,面對整數時,系統會把輸入在 HERE 處的字串,經由 NUMBER 指令轉換成數值,放在堆疊上,然後,那個 HERE 位址已經無用了,所以 DROP 掉。面對浮點數時,狀況相類似。如此形成了一個名為 NEXTI 或 NEXTF 可共用的指令,新指令名稱的意思,是準備接受一個後續輸入數字的新指令,這個指令供新的四則運算指令,共同用來設計新的執行內容。例如:加法 (+) 指令,在新的規格中,被定義成先別急著開始直接執行加法,而是透過 NEXTI 或 NEXTF 的要求,先處理一個後續的輸入字串,它必須是一個能被 NUMBER 轉換得成的整數數字,或必須是由 COUNT 、 >FLOAT 兩者組合而成,能將輸入轉換成浮點數的字串,轉換出來的數字均放置到指定堆疊上之後,才執行原本的加法運算。

如此一來,系統碰到加 (+) 指令時,就不會直接加,而是到 + 的後面去處理出一個數字才執行加法了。其它的所有四則運算指令,都被以同一方式,定義成新的執行格式,所以就完成了能夠執行中算符式的四則運算程式設計。

為了使用系統中已經用掉了的同名同功能指令作為名稱,我們藉著字彙的宣告,來區分出彼此的不同。因此,在這樣子使用四則運算時,必須先行宣告使用 IKQ 或 FKQ ,用完了也不可忘了使用宣告 FORTH ,讓整個系統恢復正常。

WORD 指令經常在想要改變單一個指令的執行秩序時,用來設計程式,不單只是處理數字時可以使用,處理文字字串也經常使用。例如:我們想改變檔案處理指令的執行秩序時,不希望檔案的全名必須放在相關操作指令的前面,而希望可以改放在後面,就如同 FLOAD 那樣的使用方式時, WORD 指令就又能派上用場。這類型的使用範例,在 FORTH 程式設計環境中幾乎是屢見不鮮,因此,使用者應該用心了解並活用 WORD 指令的實質功能。我在設計 ABC FORTH 數學計算系統時,當然也用過這些設計。

改變單一個指令的執行秩序,是藉由 WORD 指令的功能來完成;程式的執行秩序,也發展出可以改變先後秩序的技術了。

程式設計的先後秩序

這一次我們不談傳統的由下而上(Bottom-up)程式設計規矩,改談弄亂(Mess up)這種設計規矩的方法。

我在每篇網文中或多或少的都提供給讀者一些 FORTH 的相關技術,希望它們能夠傳承下去,我的目標是想將技術內容寫到最簡單的程度,要人人能看得懂,每篇文章扯到 FORTH 的內容實在不多,觀念又淺顯,所以應該很好學也很容易學,重點是您自己得動手實作設計程式,才會有真正的結果,光看文章是永遠看不出名堂的,這件事我要不斷的提醒大家。

最近在與 FORTH 相關的國際交談網站中瀏覽時,見到有人開始談論許多非英文的環境,出現了各種用 FORTH 創作的新系統。一般的意見我就不提了,其中兩項很突出的言論是:他們看不懂非英文的技術說明,但是覺得系統的性能非常傑出。另外一個突出意見就是: FORTH 的學習曲線非常陡峭(Steep learning curve),他們也同樣感受到 FORTH 的未來發展越來越艱辛。

見到這樣的言論,一則以喜,一則以憂。喜的是:講英文的人終於搞清楚了,他們不再獨霸技術,一生光講英文不學第二國語文就要不如人了,而我們一生辛苦的苦讀中文還得學英文,終於還是得到了許多好處。憂的是:陡峭的 FORTH 學習曲線普及全世界,對講那一種語言而言,統統一樣, FORTH 的確是難學!

不患人之不己知,患不知人也。只講英文的人,看不看得懂我們的技術?實在不重要,但也不可沾沾自喜,設法全面了解他們的所有技術,才是正途。

基本的 FORTH 程式設計原理,是用冒號『:』及半冒號『;』來定義出一個新指令,然後強調:『前面設計好的指令,後面的程式才能拿來用』,亦即『先造後用』,也就是說,程式設計的規矩為由下而上(Bottom-up),最後就完成了整個程式。會 FORTH 的人都知道這幾句話是老調重彈,此處則引用它來產生新話題。古時候最早期的一般 FORTH 系統確實是只能這樣用的,後來就發展出可以不按這個規矩設計程式了。中華民國已經出版過的 FORTH 基本教材,沒談或來不及談這個主題,所以就值得在這裡談。

天下事都是相對的,有大就有小(所以不要老是愛跟別人比較大或小),有高就有低(所以不要老是愛批評別人高或低),設計程式時要由下而上,思考要如何設計時,就得由上而下(Top-down)了。因此, FORTH 發展到後來,就有一些調皮工程師,設法打破這種規矩,想出了一些弄亂規矩的方法,創造實現出了新技巧。技術一直都在演進,新技巧方便、好用就被留下來,反之,就算新技巧能用 FORTH 自身設計出來,也不太有人想用,久了就被世人所淡忘。

其實,本網頁網文已公開的程式中,已經可以找到許多如此設計程式的例子使用了 DEFER 這個指令。想用來設計可以執行出任何一列數學式子的多功能性指令時, DEFER 就具有我們所談論主題的特性。它被擺置在程式的很前面,甚至於可以擺在最前面,使用當時,還不知道它被執行內容的影子到底在那裡?指令的這種性能,幾十年前,我們曾在中山科學研究院的週五夜間 FORTH 研討會上,給了它一個很恰當的中文術語,稱為:『先用後造』,或稱為『未造先用』,本文暫時統一稱之為『先用後造』。有別於『先造後用』的 FORTH 傳統程式設計規矩。

DEFER 指令不是伴隨原始 FORTH 系統而存在的指令,隨便舉一個例子,如早期的 MVP-FORTH 系統就沒有,它也沒被列入 FORTH-83 標準,所以很多早期的 FORTH 基本教材不教這個指令,它現在已經是 ANSI 標準指令之一了。

莫管 DEFER 指令的英翻中意思為何?它最恰當的指令中文譯名應該為:(宣告一個可以儲存起始執行位址用的)『向量』(Execution vector),如此,它便可以順理成章的與常數(CONSTANT)、變數(VARIABLE)、字彙(VOCABULARY)、變常數(VALUE)…等同類指令擺在一起,它們都是想宣告出某種資料結構時,必須使用的指令。

DEFER 的字典意思為『延緩』、『展期』,都不適合拿來當作指令名稱,但經其宣告而成的後續指令,具有這些意義,它表示宣告出一個可變向量名稱,並且可以延緩設計了內容後,才將起始執行位址存入此名稱,簡言之,就是可存執行位址的向量, DEFER 沒有動詞的動作,卻像變數宣告,只有宣告出名詞的意義,因此,就簡稱『向量』最為妥當。

DEFER 很好用,它也不單純的只是擁有『先用後造』性質而已,您若回頭去看看我在前面文章中的使用範例,您就可以體會出,業經 DEFER 宣告過的執行向量指令,可以有千變萬化的執行內容,只要系統的記憶體容納得下,任何在其後面設計出來的指令,管它們是否風馬牛不相及?全都可以當作它的執行內容,所以應該命名為執行『向量』。變動執行向量內容的方法,也簡單到只需使用『’』(遞給)與『IS』(是)兩個指令,便可達到目的,形如下式:

’ [new-name>] IS [deferred-name]

因此,前文的例子,只需一個程式,就可以讓讀者繪出所有合理的函數曲線。它後來才被發明出來,因為好用,就被納入 ANSI FORTH 標準。

我在設計ABC FORTH 數學計算系統時,發展到達最後階段,感覺數目字的輸入格式被系統限制住了,非常不滿意,仔細的研究過整個系統之後,發現修改核心指令 NUMBER 的執行內容,才能根本解決這個問題,而大部份 FORTH 系統都將 NUMBER 的設計內容固定下來,不允許使用者隨意更改,我得依靠執行向量的觀念才能對 NUMBER 指令的執行內容進行修正。解決的辦法就是在系統源程式的 NUMBER 指令前面,宣告出一個執行向量。

大部份國外設計出來的 FORTH 系統,都不管能否在系統中自由使用中文?的問題,想修改這些限制,通常必須修改系統指令名稱欄內的結構,而系統原本的性質還不容許被破壞,解決辦法也是到源程式中找到關鍵指令,將其內容改成執行向量的附屬內容之一,隨後就可以為所欲為的設計出適合中文環境的系統來。但對後來的大型 FORTH 系統而言,此項工程通常有點浩大,使用 FORTH 的能力,未達某種程度的人,不易辦到,進行這種工作也不輕鬆,我自知,我不行。

關於在 FORTH 系統中使用中文的問題,我們有一些經驗,分述如下:

中華民國有不少人曾在 Win32Forth 系統上下過功夫,它可以順利使用中文。
易符公司發行的 F# FORTH 系統則先行考慮了容納中文。
Forth Inc. 發行的 Swift Forth 則無此功能。
OS/2 DPMI FORTH32 系統,在 XP 環境可以執行,但也不能使用中文。

上列後面的這兩個系統,都是當初在開啟其自身所需要的一個顯示視窗時,沒顧及這項要求所致,因牽扯到如何將 FORTH 系統架設在作業系統上的技術,使用者必須從作業系統那方面重新搞起,通常難以修正。

可由 C 編譯而成的 gForth ,通常只藉一個視窗來當作系統的終端機顯示器,所以能顯示中文就能用中文,但指令名稱字長限定為少於 31 個位元組(Bytes),所以中文指令用名不可以太長。

其他的系統我未試用,啟動系統後直接試用,就知結果。

FORTH 是全面透通式的程式語言,透通到能讓使用者在系統內為所欲為,但『後果自行負責』,軍中帶兵的長官下達命令時,常用這一句話。古時候,我們想完成類似 DEFER 指令的功能時,人工操作遞給『’』指令得到 CFA 執行位址,再用 DUMP 指令仔細看可以替換的位置在那裡?配合設計一個變數,進行直接存取更換,也能達到同樣的效果,但操作起來實在太累了,所以 DEFER 是個解決問題的好指令,應該存在。

突破必須先造後用傳統程式設計規矩的指令,不只 DEFER 一個而已,也是後來才出現的 ANSI 標準的另外兩個指令: RECURSE 及 EVALUATE 也是。

EVALUATE 性能與 DEFER 有延伸性關係。比較二者,則 DEFER 形同是只能儲存單一個執行位址的向量, EVALUATE 則形同是可以執行掉被儲存之一長串執行位址的陣列,而且也是在面對必須先用後造的情況時才使用,下列的精簡測試實例,能夠協助您自然了解它的用法,不需要太多文字說明。

: DO-NOT-YET ( - - )
S” DONE1 DONE2 DONE3 ” EVALUATE ;
: DONE1 CR .” ONE DONE . ” ;
: DONE2 CR .” TWO DONE . ” ;
: DONE3 CR .” ALL DONE . ” ;

這個範例程式展示 DO-NOT-YET 指令先用後造了 DONE1、DONE2、DONE3 三個指令,它靠著標準指令 EVALUATE 以及 S”…………” (這是一組就地形成字串的指令)達到目的。因此,此後執行 DO-NOT-YET 指令時就會顯示出這樣的效果。那麼,如果 DEFER 譯成執行的『向量』, EVALUATE 就該譯成『執行字串』了,意思是執行掉前面的一連串字串陣列,它有動詞的動作(執行掉),而不是宣告出名詞。如果沒有 EVALUATE ,就可能需要使用好幾個 DEFER 才能達到目的。只不過這樣安排 EVALUATE 的指令性能,使它無法像 DEFER 一樣也可以隨時更換執行內容而已,將來若有需要,它的格式自然也就會發展成此處所描述而尚未具有的性能。

若再舉一反三推演下去,也可以設計出一個『執行矩陣』來, DEFER 是宣告出一個點, EVALUATE 是執行掉一條線,那個不知名的執行矩陣就是一個面式結構,處理 3D 的執行向量對 FORTH 而言也不是問題,接下來就不描述了。

上述造來造去、用來用去的關係也能用來解釋一個『現造現用』的指令,就是 RECURSE ,先讓我們舉例說明後,再強調它應有的適當中文譯名。

古時候的其他程式語言,例如:FORTRAN,通常稱這種指令性能為自用副程式,性能有一點特別,有些程式語言也不允許使用者如此設計程式。在我們的 FORTH 中,它的意義是『將尚未定義完成的自己納入定義』,常有人舉階乘(Factorial,例如:數學表示式為 5 ! = 5 * 4 * 3 * 2 * 1) 的計算為例,使用自用副程式的方式來完成設計。此處另舉一個反過來往上印出數字的例子,增加您對此性能的認識。程式與實際執行結果,轉錄如下:



 

\ Nursery rhyme : counting hoptoads
\ 四川民謠:數蛤蟆。
\ 作者:曾慶潭,2010年4月2日於紐西蘭,版權所有。
\ Author: Ching-Tang Tseng, 2Apr2010, All rights reserved.
\ http://forthfortnight.blogspot.com
\ ilikeforth@gmail.com

10  VALUE  LIMIT

: READY  2  ;
: GO
  CR
  DUP . ." toads, "
  DUP . ." mouths, "
  DUP 2 * . ." eyes, "
  DUP 4 * . ." legs. "
  ." Hoppity hop, hopped into the pond."
  1+ DUP LIMIT >
     IF DROP EXIT
     ELSE RECURSE
     THEN ;
: ARE-WE-GOOD? ;
: SING1 ARE-WE-GOOD? READY GO ;

: 預備  1  ;
: 唱
  CR
  DUP . ." 隻青蛙 "
  DUP . ." 張嘴,"
  DUP 2 * . ." 個眼睛 "
  DUP 4 * . ." 條腿,"
  ." 噗通!噗通!跳下水。 "
  1+ DUP LIMIT >
     IF DROP EXIT
     ELSE RECURSE
     THEN ;
: 準備好了嗎? ;
: 數蛤蟆  準備好了嗎?  預備  唱  ;
: SING2   數蛤蟆  ;

FLOAD RHYME.F
From file: RHYME.F word: GO isn't unique  ok
SING1
2 toads, 2 mouths, 4 eyes, 8 legs. Hoppity hop, hopped into the pond.
3 toads, 3 mouths, 6 eyes, 12 legs. Hoppity hop, hopped into the pond.
4 toads, 4 mouths, 8 eyes, 16 legs. Hoppity hop, hopped into the pond.
5 toads, 5 mouths, 10 eyes, 20 legs. Hoppity hop, hopped into the pond.
6 toads, 6 mouths, 12 eyes, 24 legs. Hoppity hop, hopped into the pond.
7 toads, 7 mouths, 14 eyes, 28 legs. Hoppity hop, hopped into the pond.
8 toads, 8 mouths, 16 eyes, 32 legs. Hoppity hop, hopped into the pond.
9 toads, 9 mouths, 18 eyes, 36 legs. Hoppity hop, hopped into the pond.
10 toads, 10 mouths, 20 eyes, 40 legs. Hoppity hop, hopped into the pond. Ok
SING2
1 隻青蛙 1 張嘴,2 個眼睛 4 條腿,噗通!噗通!跳下水。
2 隻青蛙 2 張嘴,4 個眼睛 8 條腿,噗通!噗通!跳下水。
3 隻青蛙 3 張嘴,6 個眼睛 12 條腿,噗通!噗通!跳下水。
4 隻青蛙 4 張嘴,8 個眼睛 16 條腿,噗通!噗通!跳下水。
5 隻青蛙 5 張嘴,10 個眼睛 20 條腿,噗通!噗通!跳下水。
6 隻青蛙 6 張嘴,12 個眼睛 24 條腿,噗通!噗通!跳下水。
7 隻青蛙 7 張嘴,14 個眼睛 28 條腿,噗通!噗通!跳下水。
8 隻青蛙 8 張嘴,16 個眼睛 32 條腿,噗通!噗通!跳下水。
9 隻青蛙 9 張嘴,18 個眼睛 36 條腿,噗通!噗通!跳下水。
10 隻青蛙 10 張嘴,20 個眼睛 40 條腿,噗通!噗通!跳下水。  Ok

請仔細閱讀並實際試用這個程式,它具有多方面的用途。

至少看看中文後再欣賞一下英文,程式裡面的文字,純粹是我個人的創意作品,不認識的單字就請查一下字典,我小女兒協助我在裡面使用了一個無意義的協助發音單字,字典上沒有。

這是一首童謠詩,全中華民國的同胞都熟悉,但您可能不知道童謠文學可以如此與程式科技結合,電腦一路唱下去,可以正確的算出好多條腿。

這樣的童謠還能幫助我們測試系統的回返堆疊容量到底有多少?人們在唱這首童謠時,唱錯的人就得停下來接受處罰,然後換人再唱,不是嗎?您不要以為電腦依此程式一路唱下去,就不會唱錯了,您可以經由更改 LIMIT 的設定值來進行這樣的測試,例如:直接執行 1000 TO LIMIT 。

能使系統算錯了腿數的設定值,就是這個系統回返堆疊的單位容量(Cells capacity),系統立刻執行例外處理。Win32Forth 為 1985 , gForth為 65257 ,出錯時系統都還可以健在,只是不再繼續執行程式。 F# Forth 回返堆疊的容量則很大,我無暇仔細測試,但知道十六進制的 40000 可使系統崩潰,您得重新啟動 F# 後才能再試。

各種 FORTH 系統的回返堆疊容量有這麼大的差異,沒甚麼好奇怪的,它端賴於設計者的系統原始規劃,通常容量也是可調的,只是您得自知調整方法。容量的大小也不代表系統的好壞, F# Forth 啟動後的新鮮狀況,能讓回返堆疊容量這麼大,判斷它係被規劃成開放式的成長空間所致,當程式跑到回返堆疊要用到系統自身的記憶體位置時,系統就崩潰了。 Win32Forth 及 gForth 的回返堆疊空間雖較小,系統卻進行了妥善的處理而不會崩潰。再說, Charles Moore 親自設計的 FORTH CPU i21 ,它的回返堆疊只有 17 個單位,設計人是 FORTH 程式語言的發明人,您能說 i21 不好嗎?

上述程式中使用 RECURSE 的位置有特別意義,它的功能為:就在這個位置填入當時指令(『GO』或『唱』)自身的執行位址,但當時系統尚未完成該指令的編譯,本不允許,例如:您直接將 RECURSE 改成 GO ,就無法通過系統的編譯(但我曾用過可以這樣設計程式的 FORTH 系統,忘了是那一個了), RECURSE 則實現了這樣的要求。了解了它的性能,再配合上述同類指令的譯名, RECURSE 因確實是在做現造現用的工作,它不是宣告性指令,卻像是動詞式的實際動作性指令,執行填入起始執行位址碼於此處的動作,所以應該被譯成『叫用自己』,您說呢?

FORTH 中使用的自用副程式概念,與其他程式語言所用者,在觀念上有點不同。 FORTH 指令間參數的傳遞只靠堆疊,但其他程式語言,次程式傳遞廣泛的資料,凡包裹在小括弧內者均是,彼此不同。除非參數只有少數幾個,否則想要轉譯別種語言的程式成 FORTH 程式時,會感到很不方便。我在使用 ABC FORTH 數學計算系統進行程式轉譯工作時,有此強烈的感覺。

自用副程式的使用要求,係必須在程式中設計了正確的中止執行條件,否則會令系統循環不已。上例中,數字大於 LIMIT 時就會中止,所以執行起來沒有問題,另外,這個可以執行自己的指令,會將自己的 CFA 執行位址不斷的推擠進系統的回返堆疊,如果叫用自己的層次次數太多,就會用光回返堆疊的結構,此一不良情況,您也得自行負責,在 Win32Forth 及 gForth 系統中,將此狀況處理成:用完回返堆疊就算了,執行會自行中止,系統並不會崩潰,所以我們才能利用這種特性來測試回返堆疊的設計容量,您讀了本文,應該自己去試一試。

至於程式設計時偶爾也會用到的性能類似指令,另有:『造了無用』,通常卻『另有妙用』的無執行內容指令NOOP。還有:『造了不給別人用』,『光給自己用一次』的 :NONAME 指令。 NOOP 係非標準指令, :NONAME才是 ,指令功能請參考標準指令之相關說明,想要熟悉它們的用法,您得參考許多高手設計的程式後,才能體會出它們的真正意義,我已見過許多,也在一些文章中稱讚那樣的用法。當然,最糟的情況就是『不造不用』、您千萬別當一個『不用』又『不造』的 FORTH 使用者。

本文大膽的開始給予論及的 FORTH 標準指令中文譯名,但譯名全都其來有自,絕不亂譯,傳承 FORTH 技術時,需要中文譯名,譯其名者也應有縱觀天下的眼光,而且要符合時代潮流,我歡迎能有如此素養的同好共同探討。

三個論述的 ANSI 標準指令中文譯名,再度摘要如下:

DEFER 向量
EVALUATE 執行字串
RECURSE 叫用自己

搞通程式設計的先後秩序,是現代 FORTH 較為強調的重點,才會有上述這些指令的加入系統現象,本文回溯古典 FORTH 的歷史,可讓讀者更容易了解新標準指令的性能,講解的方法採取了『四書』首書『大學』第一章,開宗明義的醒世名言:『物有本末,事有終始,知所先後,則近道矣。』


2026年5月16日 星期六

編寫與執行 FORTH 程式方式的歷史回顧

編寫與執行 FORTH 程式方式的歷史回顧


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

無法適應編寫與執行程式的操作環境,是正式使用 FORTH 系統前的第一大障礙。

2011 年時,流行之 FORTH 系統的使用方式為:

編寫程式時:強調使用在執行中的 FORTH 系統外面另開編輯視窗,進行檔案式編輯。也強調可以配合滑鼠來操作。

執行程式時:繼續保留在執行中的 FORTH 系統視窗內,直接輸入『載入檔案』(FLOAD 或 INCLUDE)指令式的傳統執行操作功能。但更強調從執行中的 FORTH 系統視窗上緣工具列內,操作滑鼠,選用『檔案』(File)一欄,拉下菜單式選項,由其中選擇『載入檔案程式』(Load Forth File)方式來執行。

傳統與當時流行的編寫或執行程式之操作方式大大的不同,但都有存在的價值,不可偏廢,本文就細述大家可能見到的操作情況,使用者了解之後,便能夠適應於各種情況,然後順利的使用系統。

程式編輯器概述

所有的電腦程式語言,在發展階段都需要編寫程式,編寫程式的方式也不斷的在發展,了解並習慣於程式編寫操作,就成了正式使用一個程式語言系統之前,首應學會之事。程式編寫環境確實有著優劣之分,而且關係到程式隨後被執行操作時的便利性。程式編寫功能,完全建立在系統的編輯器(Editor)上,它也是一個被設計出來的程式,欲設計出健全優秀的功能,必須在編輯器程式上付出設計代價。現代大型的程式語言系統,可以不考慮執行速度與記憶體容量的問題,因此,對編輯器功能的要求,當然是越優秀越好,編輯器程式就會被設計得非常龐大、複雜,而且僅只考慮使用時的友善性,令其好用與方便。

FORTH 程式語言,其程式編輯器的設計要求,大體上與傳統程式語言相同,但由於語言特質不同於一般程式語言,有時, FORTH 的程式編輯器還反過來不求方便,而將發展重點集中於自身系統性能的積極改進上,甚至於根本不設計編輯器,而改採別人提供現成可用的編輯器。理由無它,僅因 FORTH 系統太容易自行建立,而自建於作業系統中時,因作業系統願意公開的資源有限,妨礙編輯器的設計,於是延伸出許多FORTH系統不太在乎編輯器性能是否優良?卻照樣被愛用者高度接納的不合理現象。

為了適應流行的趨勢,歷代暢行的 FORTH 系統,亦均有其符合當時趨勢的優秀程式編輯器,還隨時都不斷有人為當時編輯器進行性能改善設計。換句話說,好的編輯器都不是一次性完成的產品,此一現象也與一般程式語言的狀況不同, FORTH 系統一切透通,這樣子發展也很自然。 2011 年流行的方式,是在 FORTH 系統操作視窗環境之外,另開視窗編寫程式,其中功能卻不斷更新,但主要功能只有幾項,仍然容易掌握。將來的編輯器必然與現行者不同,根據以往的歷史, FORTH 不會在乎,也不需要在乎別人的發展, FORTH 完全可以配合時代潮流來演進,因為系統的任何部份都很容易自建。

一般程式語言系統的編輯器,通常是與系統主體可以明確隔離開來的,也就是說,編輯程式時,就只編寫程式,不做其他事情;編譯程式時,就只做編碼之事,也不再涉及編輯之事。 FORTH 不同,所有系統內的任務都可以混著執行,編輯器也與系統主體完全連在一起,因此,程式編寫時就也同時考慮到隨後要交給系統執行之事;系統執行程式時不排除可以執行涉及編輯器的任何事情,甚至於可以共用資源或交互支援,這一方面, FORTH 與一般程式語言不同。

編輯器的演進歷史

第一套被設計出來的 FORTH 系統,可能沒有也不需要編輯器,因為 FORTH 是即時性控制(Real-time control)程式語言,系統啟動之後,直接輸入已經存在的指令,就可立即得到執行結果,所以根本可以不用編寫程式用的編輯器。

一直到今天,全世界仍有大量的使用者喜歡為單晶片電腦加裝自己設計的簡易 FORTH 系統,系統可以執行出結果後,為了能夠方便的編寫可以留存的程式,自行設計一個程式編輯器就不可或免,編輯器功能多,編輯器程式就不可能很小,因此,功能恰到好處的編輯器程式反成了值得永遠留存的設計作品。

最早的 FORTH 教材,介紹列式編輯器(Line editor)。使用者可以使用幾個簡單的單個字母,操控編寫出來的程式,將其留存在指定的記憶體緩衝區,感覺上性能相當粗糙,但是,您在自行發展自用系統的初期,簡易編輯器就能滿足編輯需要,還大量節省了記憶體的使用量。編輯器通常不會是您想發展出應用系統的唯一目的,換句話說,最精簡的列式編輯器,永遠都有其存在的價值。如果您的確具有想要自己發展 FORTH 系統之念頭,自行保留一份先聖先賢留傳下來的優良列式編輯器源程式,是必要的。

古早以前的電腦,顯示裝置很簡單,只有一列 20 或 40 個字元,能夠上下滾動顯示的緩衝量也只能三或四列。 隨著時代的進展,電腦系統的輸出顯示,演變成現今的螢幕之後,編輯器也改採以幕式編輯器(Screen editor)為主了。囿於傳統的 FORTH 系統曾將大批儲存(Mass storage)媒體,例如:磁碟,規劃成記憶體用量,每 1K (1024 bytes) 為一個區塊(Block)來操作,較早期的幕式編輯器所設計的編寫程式範圍,也因此以每次顯示、讀、寫 1K 的程式量為主。那時,游標的控制、訊息的插入、刪除、複製、貼上…等各種功能也都陸續被設計出來,好的程式編輯器,確實可以大量節省了使用者發展程式的時間,在電腦系統內記憶體還不便宜也無法大量使用的時代,編輯器佔用記憶體的數量卻成了另外必須考慮的問題,設計者只好在性能與環境適應性上進行均衡取捨。

嚴格說來,幕式編輯器依然延用至今,但細部分析時,幕式編輯器又進行過幾次明顯不同的演進。各個階段主要的差異,起因於後來電腦作業系統,對儲存資料的方式,採用了檔案(File)的觀念,與 FORTH 傳統的區塊觀念不同,編輯器的設計只好隨之而變。

區塊式檔案是一種 FORTH 獨有的檔案規劃方式,這種觀念一直延用到今天,仍然可以發現它的存在,道理無它, FORTH 就是可以很容易獨立存在於任何電腦中,可以完全不依靠作業系統,還喜歡直接對一切硬體的實體位址進行存取操作,以 FORTH 自己發展自己時,就強調這種區塊式檔案的規劃比其他方式都好。使用 FORTH 的能力到達能夠獨立進行發展的程度前,使用者必然早就熟悉各種程式編輯器的操作了,所以區塊式檔案編輯系統的操作說明,實不必特別強調說明,需要使用的人,通常有能力自行了解。

現行檔案式檔案是目前流行作業系統中採用的主流檔案規劃觀念, FORTH 在由區塊式檔案過渡到現行一般檔案式檔案規劃期間,也曾有過臨時變通式的安排。當時,曾採用過以一個流行檔案來包括固定數量區塊的辦法,讓檔案中有區塊,後來,才逐漸的讓區塊式觀念從現行檔案格式中自然消失掉,大部份現行 FORTH 系統,都已經與一般檔案觀念同步,目前,已經罕見仍採用區塊觀念設計的系統。

當 FORTH 的程式編輯器發展到與一般文書處理系統的操作結果相容時,對 FORTH 系統而言,會衍生出許多不能自由載入檔案讓程式進入系統的問題。文書處理系統只強調文件編排,為了美觀與多樣性的功能,所產生的檔案必須包括基本文件所需要的格式碼,這些東西不是 FORTH 系統能夠接受的程式,因此, FORTH 系統不能任意載入一般文書處理系統所編輯產生的 FORTH 程式。

就目前流行的微軟作業系統常用的文書編輯系統而言, Word 編輯器的產品不能直接供 FORTH 系統使用,因其最簡檔案的檔頭內,幾乎有 2K 的記憶體容量,放的是格式資訊。 WordPad 編輯器的產品也不能直接供 FORTH 系統使用,因其最簡檔案的內容,每一列結尾處都安排了特殊的跳列格式碼。只有號稱最簡單的記事本(NotePad)編輯器的產品沒有問題,這是一個幾乎可以稱作完全素文字式的文書編輯器, FORTH 系統就需要與記事本文書處理系統相同的程式編輯器功能,用記事本編輯出來的源程式,完全可以被 FORTH 系統接受。

如果只是單純的編寫程式,現行 FORTH 系統就不需要另行設計自己的程式編輯器了,因為使用系統的記事本可以解決編寫程式的問題。前曾述及, FORTH 系統絕對可以在編輯程式與系統執行狀況之間進行功能串聯,於是, FORTH 系統便仍然自行設計出自己的程式編輯器,就算編輯器的操作功能只等同於記事本,也仍然要這樣做,尤其在強調載入程式期間的輔助除錯顯示功能時,只能靠 FORTH 系統與自己設計出來的程式編輯器直接串聯才容易辦到。

許多電腦使用上的新觀念,也影響程式編輯器功能上的發展,例如:捲動式視窗顯示, FORTH 系統的編輯器也已採用了。滑鼠的操作功能也不例外,現行的 FORTH 系統還很強調必須配合滑鼠的操作,才能毫無問題、快速完成載入程式與執行的操作。例如:編輯產生的程式檔案名稱不是例行顯示文字時,難以單靠鍵盤操作直接輸入檔名,不用滑鼠點選便不能載入系統。另外,因滑鼠操作具有反白選擇、複製、再貼上的功能,有許多狀況,不用這種方式操作就無法將程式載入系統。直接操作滑鼠,當然也比逐個字元的打字操作,要快太多了。因此,現行的 FORTH 編輯操作不能免除滑鼠的功能,並且必須特別加以活用,才能節省程式發展的時間。

時代的進步不會中止,未來的電腦系統,必然不斷出現大眾認為方便的系統操作功能, FORTH 不能自外於世界發展,必須跟進,所以,可以預卜, FORTH 自己的程式編輯器,還得隨著時代的演進而發展。最近,電腦使用觸控螢幕,有大量增加的趨勢,隨後, FORTH 程式編輯器的新設計,可能就會受到影響,這種演變絕對不會停止。但是,程式編輯仍然有其絕對基本的單純觀念可以依循,使用者只是需要花一些時間來適應系統要求的操作,要學好 FORTH ,這就是一件非先行學會不可的習慣性操作。

編輯器的演變歷史:

第一套 FORTH 係直接操作系統,不需要編輯器

一列式編輯器

一幕塊式編輯器

一幕檔案式編輯器

視窗配合滑鼠操作檔案式編輯器

可能為多元控制或非現行檔案格式之編輯器
(配合觸控、聲控、圖示、輸入影像、腦波意念式控制……等多元操作)

.......... (作者也無法預卜之方式的編輯器)


典型的程式編輯器

以 Win32Forth 系統為範例來說明,比較容易詳細了解典型 FORTH 程式編輯器的整體性能。

中文顯示現在已經成為正式電腦的固有性能之一了, FORTH 系統的發展則尚難完全跟進,這也是為什麼我選擇採用 Win32Forth 系統來介紹程式編輯器的原因之一,這個系統配合中文的要求性能,已經完全在我們的掌握中,全世界現在流行的大型 FORTH 系統,還有許多個系統的性能無法達到適應中文的要求。

Win32Forth 系統經過十幾年的演進,陸續出現過許多版本, 2011 年的版本為 V6.14 版,也有其新版編輯器。系統的發展歷史,不是此處打算介紹程式編輯器時的重點,故略而不談,只談單一個有用的編輯器程式。

為了迎頭趕上最新的版本,並且涵蓋整個 Win32Forth 新舊系統均可使用的程式編輯器要求,只介紹系統中最有彈性功能的程式編輯器 WinEd ,但並不需要逐項仔細的介紹出這個編輯器的所有功能,只挑選實際編寫程式時,足夠用來完成工作的操作予以說明,未介紹到的其他功能,讀者習慣於使用這個系統後,可以自行透過試用的方式來了解。

此系統中另有許多其他設計者提供的程式編輯器,若單純只以中文顯示為著眼點來考慮,只有 WinEd 的性能較為健全,也較為精簡,我們就以利用 WinEd 編輯程式,編寫出最簡單的一列迴響印出文字的程式,當作精簡範例,作為開始,並刻意以中文作為檔案名稱,再以滑鼠操作載入檔案,實現整個精簡過程。一般的大型程式編寫操作方法與此雷同,熟悉這個精簡操作過程,就足夠用來設計一般程式。

隨後,再根據作者長期使用此系統的經驗,將逐步介紹其他在編輯程式時,必須妥善利用的特殊操作,以便學會遇到特殊情況時,解決某些困擾的辦法。程式編輯器的所有功能,都不是必然的設計,是許多人使用過後,再經不斷改進設計的結果。另外,包括融入中文或非英文的其他文字,儲存成一個 FORTH 系統能夠單獨識別出來的檔案格式及其圖示(Icon),滑鼠、觸控、色彩、聲效 ..... 等的功能,都必須配合基礎作業系統來設計,背景作業系統缺乏這些功能前,編輯程式也就不便單獨為其建立這些功能。

就像啟動 Win32Forth 系統的方式一樣,啟動 WinEd 時,也是利用滑鼠,在螢幕上那一片以綠葉子圖示的程式編輯器位置,直接點選兩次,系統立刻進入可以編寫程式的新視窗環境。

一開始,我們可以完全忽略視窗中所有邊框的顯示,直接找到螢幕上閃動游標所在的位置,立刻開始編寫程式,例如:就鍵入下列一列程式

: hi .” Hello ! How is going? ” ;

表示我們設計了一個名稱為 hi 的新指令,它的工作就是拓印( .” ..... ” )出一列文字,此處為『 Hello ! How is going? 』,然後就結束了工作,它就足夠代表一個完整的程式了。

接下來的操作,雖不是標準 FORTH 系統所強調的東西,卻是能不能達到讓 FORTH 系統順利載入這個檔案程式的關鍵,為了此後能快速操作系統而節省發展程式的時間,使用者必須熟悉這些操作且養成操作習慣,作者於發展大型應用系統期間,強調曾經一日之內反覆進行了成千上百次這樣的操作,一點都不為過。

WinEd 非常強調藉由滑鼠來完成工作,因此,要存住這個剛編寫完成的程式時,最好利用滑鼠。雖然壓鍵盤的 Ctrl+S 也能達到目的,但可以不用去記憶這樣的操作,像一般文字編輯系統一樣,這種壓鍵操作的功能,大概只在滑鼠功能失效時還有點用途。

WinEd 也採用自上方主要功能分類指示文字中,點選一類,以取得此一大類菜單式子類分支功能選項的設計。從文字說明式的菜單選項挑選一項,操作滑鼠滑動到此一選項,則此被選之項的文字將反白顯示,再以滑鼠左鍵於此位置點選一次,系統便按功能要求執行指定的程式。

第一個要學的操作是存起新建檔案:先點選左上角的『 File 』(檔案)大類,然後在菜單選項中點選『 Save File 』(存檔),因為這是一個新編寫的程式,所以才這樣選擇。

編寫一個原就存在於系統的檔案程式,則係經由 WinEd 以『 File 』『 Open File 』(開檔)操作,自另外顯示的小視窗中找到想要編寫的檔案程式,開啟此檔案程式,編或改寫完成之後,存檔的操作則為『 File 』『 Save All Changed Files 』(存起所有業經改動過的檔案)。在程式發展過程中,這樣的操作,比上述建新檔案的操作更為頻繁。

此處打算儲存起來的檔案,稱為文字程式檔案(Text file),有別於可被電腦系統直接執行的系統程式檔案(System program file),也有別於單純用來儲存資料而非程式,但也是文字形式的資料檔案(Data file),現行電腦系統,也將一個可以包括上述所有型式檔案的資料夾,視同為一個檔案,它的名稱就叫作資料夾檔案(Directory file),它們都是一般常用檔案之各別的名稱。

當使用者進行了上述存檔操作之後,電腦系統會在螢幕上顯現出另外一個小視窗,讓使用者安排打算儲存檔案的去處,這是使用者必須學會的第二項重要操作,熟悉這個操作,並建立實質觀念,此後才能明確的知道,自己設計出來的程式,是以甚麼指定格式?儲存在電腦內的甚麼地方?

尚未建議使用者在 Win32Forth 系統中新建自己專用的資料夾前,此處的操作範例可將新編成的程式檔案,暫時儲存在與 Win32Forth 系統相同的資料夾之下,以便容易進行後續操作,並明確知道檔案被儲存的資料夾位置。

確定資料夾在路徑中的位置,是進行這項操作時必須注意的事情,也是打算重新編輯一個已經存在電腦系統中的檔案時,必須注意的事情。出現的小視窗,與編寫一般文字文件時的檔案存取操作方式完全相同,因此,您若已經熟悉這種操作,便能很快體會出其中的意義。改變資料夾所處位置的層次時,以點選小視窗上方帶有翻頁箭頭的圖示來達到目的。小視窗最下方一列的檔案型態,決定了小視窗中可以顯示的檔案形式。最重要的一項操作,則為選擇『確定』欄位,壓下滑鼠左鍵前!必須確定有您自己決定賦予的檔案名稱,而且型態延伸(type extension) 尾綴用字,必須人為鍵入!!!指定為『 .F 』或『 .f 』的格式型態,否則, FORTH 系統自己無法識別出,這是一個屬於系統可以自由操作的標準 FORTH 源程式檔案。

假設存好了編寫的程式檔案後,電腦系統回到了開機備便後的一般螢幕顯示狀況,等待使用者進行下一步操作。

第三個要學的操作是載入檔案:在電腦系統的觀念上,剛才編寫完成的檔案,它的地位等同於一個 FORTH 系統,只不過 FORTH 系統是一個可以被電腦直接執行的檔案,而編寫完成的檔案,是一個文字形式尚不能執行的檔案。使用者必須操作電腦,以 FORTH 系統載入文字程式檔案,剛才所編寫完成的檔案程式,才能在 FORTH 系統中執行。

載入程式的方法,可以像傳統的 FORTH 系統一樣,在螢幕畫面上點選兩次 FORTH 系統的圖示,讓 FORTH 系統開始執行之後,使用 FLOAD 或 INCLUDE 現成指令來達到目的。執行這種指令時,必須注意的事項為: FORTH 系統現行指定路徑(Path)的處境,必須與打算載入檔案之路徑的處境,處於同一資料夾(Directory)之下。只有這樣,才允許在 FLOAD 或 INCLUDE 指令之後,直接使用指定的檔案名稱,且檔案名稱型態延伸尾綴,必須是『 .F 』式的 FORTH 系統專用格式。若檔案並非處於上述資料夾內,輸入的檔案全名,則必須是包括前引路徑的全長名稱。具有此種限制性的典型指令操作方式,例如:

FLOAD filename.F 或 INCLUDE filename.F

另外一個比較有效率的程式載入方法,為直接以滑鼠操作。

就 Win32Forth 系統而言,我們強烈建議使用者,養成這種可以快速載入程式的操作方法。一方面是可以節省程式的發展時間,另一方面當程式的檔名為長串中文名稱時,依靠上述傳統的載入檔案操作方式,就顯得太不實際了。能在整個 FORTH 系統中自由使用中文,是現行 Win32Forth 系統的一項主要特色,現在還很難見到別的程式語言能夠全面融入中文, Win32Forth 系統既然可以,為什麼不讓這樣的特性盡量發揮?假設使用者已經習慣於輸入中文,如果上述剛建成的檔名給的是『範例操作程式.F』,在練習操作載入程式到達熟練的程度前,不用滑鼠來進行載入操作,就會是一件令人相當苦惱的事情。

以滑鼠操作載入檔案程式的詳細步驟如下:

以滑鼠點選兩次,啟動 Win32Forth 系統後,此系統呈現的標準視窗上緣,有一橫列英文文字分類的操作選單。

將滑鼠移到顯示 File(檔案)的位置,點選一次,立刻會在此處以小方塊顯示隸屬於 File 此類之下的所有子項菜單式操作選擇。

將滑鼠移到 Load Forth File… 位置,點選一次後,與存檔操作時類似的另一個小視窗就會顯示出來。

操作滑鼠,到打算載入系統的檔案名稱位置,點選一次,這個檔案的名稱就會自動跳入小視窗下方原為空白的『檔名』欄內。

移動滑鼠到小視窗右下角顯示『開啟』的位置,點選一次,小視窗消失,而載入效果就會立即呈現在 Win32Forth 系統的標準視窗內。

載入成功,則會有一列包括全路徑的檔案名稱先行顯示,緊接於後顯示 ok 。

載入失敗,則顯示一大堆告訴使用者為何失敗的文字說明。

依上述操作載入成功之後,就在 Win32Forth 標準視窗游標顯示的位置,直接輸入練習建立的『 hi 』指令,可以立刻看到印出『 Hello ! How is going? 』的效果,這就是 Win32Forth 。

其他的程式編輯器

前節所述程式編輯操作的整個過程,足夠應用於一般性的程式編寫了。

視窗系統提供了使用者可以開啟多重視窗的功能,因此, Win32Forth 系統也允許使用者執行此系統時,可以開啟其他的視窗,甚至於系統在執行過程發生反常例外(Exception)現象時,有時會自動以另外一個小視窗來顯示警告訊息,或自動跳出另一個視窗,顯示引致系統執行錯誤的程式檔案內容,以利使用者立即進行除錯前的檢視。

現行文書編輯系統,很強調依靠滑鼠操作,由左上角向右下角拖曳於指定文句,使指定區域反白顯示代表被選定(Selected)了,再到操作選單中點選 Edit 『編輯』類,在菜單式選項中選取 Copy (Highlighted Text)『複製』,或選取 Paste (to Keyboard)『貼上』,讓系統來處理文書編輯時常執行的文字編寫功能。這樣的操作是 Win32Forth 系統發展程式時,經常要做的操作動作。

能夠同時開啟好幾個視窗,能夠使用滑鼠操作選擇(Select)、複製(Copy)、貼上(Paste) ..... 等功能,『 WinEd 』程式編輯器的功能,顯示它就是一個一般性的文書編輯器,與流行的文書編輯系統比較,『 WinEd 』的功能接近標準的素文字編輯器『 NotePad 』,但與『 Word 』或『 WordPad 』不同,因此,由『 NotePad 』編寫而成的程式, Win32Forth 系統可以接受,另外兩個則不行。雖然如此,『 WinEd 』與『 NotePad 』對於 Win32Forth 系統而言,意義不同,因為, Win32Forth 系統可以直接對『 WinEd 』操控的檔案操作,但系統沒有直接對『 NotePad 』操控檔案操作的相關指令,由這兩個編輯系統顯示同一個檔案時,畫面有所不同,就可以明顯的區別出來。

大部份 eForth 系統的程式編輯器,由於系統性能強調短小精悍的關係,均不注重於增進編輯器性能的設計,有些用法還根本就不設計編輯器,而依附在對其作業的大型 FORTH 系統,以便控存發展中的程式。作者使用過可以接受單純以 PE II 編寫程式的 eForth 系統、或檔案幕式的編輯系統、或檔案塊式的編輯系統、或自行開啟新視窗式的檔案編輯系統 ..... 等,也曾倣效 polyForth 系統提供的資源,自行設計過 eForth 的傳統塊式程式編輯器。這些編輯器的操作方式因系統而異,不在討論範圍,讀者必須於使用各該系統時,自行適應。


2026年5月2日 星期六

自建動態連結程式庫檔案

自建動態連結程式庫檔案


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


本文係十五年前還用 Win32Forth 系統時所留下的文章,是為了使用 FORTH 系統外部資源時所需要建立之一種觀念的文章記錄。

這是一種有關 FORTH 系統中,叫用微軟作業系統內應用程式界面(API)的觀念問題。這原本不是我喜歡談論的問題,我不喜歡 API ,所以也一直不去碰觸,我個人有能力完全隔離 API ,繼續輕鬆使用任何新出現的 FORTH 系統,對許多人而言,尤其是 FORTH 的新進使用者,則可能不習慣這樣子使用 FORTH 。

API 的出現,確實因大量污染了 FORTH 系統,而影響許多人保持接觸 FORTH 的意願,影響了FORTH 社群的發展,非我所願。有障礙,我們就突破,不如直接研究 API 。

我可以舉一個很簡單的實例,將 API 建進動態連結程式庫(DLL)式的檔案,由 FORTH 系統來自由叫用,讓大家都可以輕鬆的了解,這種使用的技術是如何被引進 FORTH 系統的?此後您就不用再摒棄它,繼而善用它。為此,我才在 60 歲時重新接觸 C 的環境,也只有這樣,才能解決這項別人不願意解決的問題,解決問題的過程,也算得上是一項寶貴的經驗,留下記錄,提供大家參考。

我有能力將實際範例寫得非常簡單易懂,只需四列簡直不像程式的 C 語言程式,而且其中還有兩列只是單個大括弧,就能辦到這樣的事情,但您得仔細的看完本文。這樣的教材,期望能夠告訴大家,精簡的傳遞我們專精的 FORTH 技術是很重要的,如果您是 C 式程式語言專家,也請不要輕視像這樣的 C 程式,它足以解釋許多人搞不清楚的問題,我相信任何人只要肯做,絕對能做得比我還好,我指的是要比四列還少,長篇 C 程式會讓 FORTH 的使用者看不懂,我實在是忍不住了才這樣做的,中國人避免用複雜問題欺侮中國人,四列就夠了。

開始研究問題時,上網瀏覽相關資料是免不了的事情,但很難找出一篇純粹以 FORTH 立場來實現這項技術的精簡文章,也就是說,還沒有這方面正式的相關 FORTH 教材,可以直接翻譯來供給大家參考使用。

我在完成解說問題前,習慣於自己先行實現論述的內容,網路上可以找出無數不在乎 FORTH 而偏重於 C 方面的資料,講述如何在 C 環境中建 DLL ,例如:您就由 Google 鍵入『building and using DLLs』,有數不清的文章可以免費閱讀,我看了上百篇,這些東西不像 FORTH ,是完全不透通的資料,您不能過問或自己追查資料內所敘述的操作步驟,生產 C 編譯器的公司發行產品時,資料內寫了使用者該這樣做,您就只好照著做,否則得不到該系統告訴您的結果,可是得到的結果卻不能用。我使用 FORTH 幾十年,從來沒受過這種氣,非常委屈,為了完成本文,我忍下來了,也希望非得用 C 來解決問題不可的電腦程式用法,這是最後一次。

網上資料通常使用了很大且空洞的篇幅,與花俏又多色的畫面來美化成品,當我記不住一大堆指定操作命令或參數用名時,想將關鍵對象摘錄下來,也出現了許多麻煩,要浪費時間去掉那些沒價值的東西,然後才能以比較節省成本的方式,將重要資料印出來仔細研究,避免浪費紙張、墨水,凡事我都講求節省,我只重內容而不重外表,因此,不會浪費紙張來印出視窗畫面。

想寫本文之前,心中早已具有在 FORTH 環境中如何使用 DLL 內之 API 的觀念,只是不曾依樣自建一個來試一試。這一次這樣做,當然不可能一次就能成功,而且每次在純 C 環境中試出一個成品後,滿懷自信的相信『 C 號稱其轉置能力比別種程式語言強』之說法,立刻就將結果轉進 FORTH 試用,但送進 FORTH 就是不能實現,結果不但不成,問題一耗就是三天不能前進。起先,我以為自己多年不接觸新進電腦系統,沒搞清楚一些奇怪的電腦術語,誤解了說明資料的意思,特地在參加本地 WLUG 組織 Linux 例行活動時,請教這裡的專家,是不是我誤解了術語?做了錯誤的『死性』操作。結果不但是無功而返,反而是我得大費口舌,必須對 Linux 專家講解 C 中的編譯程序概念,當然這些概念也不是我發明的,全是由其他相關資料讀來的,卻對我想解決的問題毫無幫助, Linux 專家大部份也對這些『死程序』一知半解,規定性的說明資料實在太多了,只講英文的電腦專家也看不懂那些英文,但那些英文沒有字典上查不到的單字。

我人在紐西蘭,能直接來往的環境中,現在已經沒有會講中文,還具備電腦技術與共同興趣的朋友,網路上呼叫台灣的朋友,實際上也沒那麼方便,鑽牛角尖的問題只好靠自己了。但是,我還是相當幸運,我有一個在大學裡主修過電腦科學的女婿,而且他仍在電腦科技的領域工作,只要我將電腦問題解釋清楚,通常他都可以協助我解決關鍵問題。女婿平日上班時要忙他自己的工作,假日裡需要累過幾天工作後的休息,我只好自己先行釐清問題,過濾出非問不可的項目,與女婿約好時間逐項解決,能完成這本文,寫下這些記錄,不得不感謝他的協助,這裡只有他能完全聽懂我向他解釋的 FORTH 觀念,他不用 FORTH ,但能幫助我解決 FORTH 以外的電腦問題。

從 FORTH 環境叫用 DLL 中的 API 技術,不是近幾年的最新技術,自從 DLL 使用資料公告給大眾,以便大家可以用它來發展軟體產品時, FORTH 也就同時建立了這項使用方法,但在 90 年代以前,沒有 DLL 的時代, FORTH 系統中不建這種功能,我們卻早已用過這樣的呼叫,只不過以前稱為作業系統 BIOS 中公用中斷程式 INT xx 的功能呼叫而已。

既然這是一項老技術,大家又不熟悉,要搞清它,最好就從一個 FORTH 系統的發展歷史資料中探討相關的問題,如此便能了解當初系統設計這項功能的來龍去脈。我們需要透過一個目前共同推廣的系統才能進行溝通,以 Win32Forth 系統為主,所以大家最好也應了解這個系統存在的可貴。我在網文中找到系統原始設計者支湯姆(Tom Zimmer)回答大眾的信件,才明確的知道某些動態連結程式送進 FORTH 系統時,跑不通的關鍵問題所在。我有種感覺,無論您學那一門學問,這門學問的發展歷史也該仔細讀一讀,否則就很難了解這門學問的精要,此即一例。另外,我從網文中也讀到了,當年歐洲 FORTH 組織,曾因 Tom Zimmer 公開 Win32Forth 系統,供大家免費使用,而頒給他一份金質獎章,這是我在 FORTH 界從未見過的殊榮,也令我更加尊重 Tom Zimmer 及其 Win32Forth 系統。

除此之外,我根據歷史記錄,仔細回顧所有業經公開之 Win32Forth 的所有版本,才知道只有 Fig 總會網站上提供的最原始 V4.2 版是原作者的創作,此後的所有其他版本,均經其他組織刻意修改過,而主要的修改內容,竟然就是以配上後來新出產的 .DLL 檔案內不同之 API 應用,為主要區別所在。讀者可以回顧 V4.2 原始版本的內容,眾多檔案中只有一個容量 382K 名為 wincon.dll 的檔案為這個系統所用 API 的主要來源,沒有其他的 .DLL 檔案了,後來的版本都加裝了其他的 .DLL 檔案,以便取得其他功能的 API 。

因此,V4.2 版以後的 Win32Forth 系統都很花俏,注重畫面表現,雖也修正了幾項後來發現的錯誤,實質內容並沒有很大的改變,但卻將幾項傳統上很有用的 FORTH 專屬性能,刻意的埋沒掉了,例如:將很有用的蛻變編譯(meta compile)操作,轉換成只選畫面中一個指定字母的『死性』操作,這樣做就讓後來接觸 FORTH 的人,更不容易了解蛻變編譯的概念。發行 V4.2 以後版本的幾個組織,好像有意扼殺 FORTH 的研究發展,希望大家只用他們做給您的系統就算了,這些新版本的性能除了畫面漂亮外沒有特出處。

只有一個組織,比較老實,網頁畫面記錄,直到 2009 年,依然只推行 V4.2 的版本,我完全能體會各個組織的用心,但我不願逐一介紹,或區分這些網站。 Tom Zimmer 個人已經不再在 Win32Forth 系統上下功夫,以後的路子要靠使用者自己走,如何走?也由使用者自己決定。也許了解這些歷史,才有一面鏡子,能夠照亮過去、現在與未來。

當初,我只選擇 Fig 總會提供的標準 V4.2 版本發展 ABC FORTH 是正確的,但我也不排斥別人的成就,我曾誇口,任何具有浮點計算功能的 FORTH 系統,我都有能力為它加裝我所設計、可用 50 年以上的 ABC FORTH 系統。目前號稱最新推出的 Win32Forth 版本是 V6.15 版,我當然也能加裝,為了實現我的信念,我就重新在這個版本上實現了同樣的設計,做完了這件事情,我就知道這個版本的問題在那裡了,所以敢進行上述批評。

不僅如此,我早已在數個 FORTH 系統中完成了 ABC FORTH 系統,但沒有必要繼續釋出,理由有三:
一是我不希望已經公開之 ABC FORTH 系統內的指令,被隨便更改,造成因為指令不標準,而無法使用那份『使用說明』的問題。
二是屢次重新編譯我的原始設計,是一件很容易辦到的事情,這樣做當然也容易造成它的不穩定,因此,我刻意將其固定下來後才公開推出。
三是我堅持暫時只在指定地區使用,一切相關發展便易於掌握,目前按照我的安排與大家的認真配合,應該可以達到這樣的要求。
ABC FORTH 系統架建在 Win32Forth 系統上,這個系統的所有問題,應該也都屬於我該關心的問題, API 是個問題,我就理所當然、義不容辭的應該協助解決,現在讓我們來仔細探討, API 是如何建在 DLL 中供 Win32Forth 使用的?

從網文上可以找到的相關資料,都是站在 C 的立場解釋 DLL 的,那是擅長 C 式程式語言者的做法。我要一反常態,從 FORTH 的立場來解釋這種應用,而且將純粹屬於在 C 環境內,如何叫用 DLL 中 API ?的問題排除掉,完全不予討論,那是只會搞 C 的人,才需要關心的問題,與我們無關。

在 Win32Forth 中,想要叫用一個 .DLL 檔案中的 API 來用時,可以簡單到只需使用兩個簡單現成指令 WINLIBRARY 及 CALL 。

假設,有一個在微軟作業系統中堪用的動態連結性程式,檔案的名稱為 mydll.dll ,而這個檔案內有許多具有指定名稱的『功能性程式』(請注意,並接受這個我所使用的適當專有名詞,如果您將這個術語套到文章中所有使用了 API 的位置,那麼,文章唸起來就通順了,而且,意義也淺顯了),在 C 中的術語稱為函數(function),它們實際上就是大家所謂之可以被外部程式叫來使用的 API 了, Win32Forth 也可以隨意叫來使用。我們再度假設,有一個典型的 API ,它的名稱就叫作 subb ,它的功能是將兩個整數相減後得到差值。就憑這兩個假設,就足以讓我們解釋清楚本文打算論述的主題了。此處『mydll』及『subb』是隨意給的命名。

mydll.dll 檔案一旦被放置在與 Win32Forth 系統同一個資料夾中, Win32Forth 系統執行起來之後,想直接使用這個 API 的操作,就只需要執行下列輸入指令,指令當然也能被設計進程式:

WINLIBRARY mydll.dll
10 3 CALL subb

其中,第一列是用來宣告將 mydll.dll 檔案的所有功能,連接進入 Win32Forth 系統所需使用的宣告式操作指令。而 subb 是檔案中的一個功能程式,亦即 API ,它此後就可以自由的被 Win32Forth 系統,以單一個 CALL 指令,如同上述的方式直接叫用了。這樣的教材只能告訴大家, Win32Forth 中要如何叫用一個API ,對已經存在此 FORTH 系統中,與微軟作業系統相關的原始 API ,確實只用 CALL 指令叫用就夠了,但它只是簡例,全面正式的使用,則另有當其他狀況出現時,必須注意的使用規則。例子舉得太簡單了,用法就不容易記住,但如果我教您如何親自來自製出這麼樣的一個 API 後,您就可以終生記得它的用法了。 接下來,我們不得不碰 C 了,但可以不必學得那麼多,有概念將其實現才是最重要的關鍵。

我剛開始嘗試設計這個教材時,規規矩矩的建立了好幾個標準的 C 程式語言的測試程式,吃過不少苦頭,唸了許多很基本與 C 相關的書籍,還做 60 歲自修老學生的筆記,記下 C 標準程式中有那些東西,例如:前處理指引、標準庫存程式、簡化程式中變數名稱之宣告方式、程式主體、程式敘述集合、庫存程式指令……等等等,一大堆術語,然後糊里糊塗的在 Linux 系統跑通了幾十個程式,到現在還是糊里糊塗的使用C系統,只會用系統來產生 .DLL 的檔案,但不會純用 C 自己設計一個能產生 .DLL 檔案的編譯器(Compiler)。

請不要笑我的學習經驗記錄怎麼這麼幼稚? BORLAND 公司才能設計出 TURBO C 之 compiler 的,我是何德何能啊?才玩幾天 C ,就想自己設計 compiler!直到現在,大約它外部 90% 的系統操作命令我都還不會用呢。可是,憑良心講,如果我已經在 FORTH 系統中下了同樣時間的功夫,我應該可以設計出迎合這種需要的一個簡單編譯器了,所以我是不是太小看 C 的 compiler 了?我不堅持我的答案應該是『是』或『否』,但一切事在人為。單純的用 FORTH 寫出能編譯出 .DLL 檔案的一個編譯器,不是大話也不是笑話, FORTH Inc. 發行的 swift FORTH 早就已經能夠辦到了,這個系統可以用 FORTH 直接設計出 .DLL 的檔案,十幾年前就見過它的廣告詞,幾萬元台幣才能買一套,所以大家都不知道,我也沒用過。學會能產生 .DLL 檔案的編譯器,對我而言並不困難,因為我熟悉 FORTH ,能讀通 swift FORTH 的程式,所以不是笑話。我雖對 C 只是一知半解,但已經敢寫這篇文章,教大家 FORTH 需要的部份了,這個教材裡面,一點都不需要涉及將指標指來指去,在 C 中耍帥的技術。

剛開始寫成的幾個程式,都還有 C 的規矩,等到我搞通問題,將結果送進 FORTH 系統,實際執行成功後,我就花時間搞調皮處理,目的就是想寫出本文,教大家一個簡單到不能再簡單之程度的範例,讓大家終生記得。

我的程式只有下列四列:

int subb(int x, int y)
{
return y – x ;
}

就這樣,沒了。這就是我全部的源程式教材,是不是很可笑?可是,它通過了編譯,編譯過程中沒有出現任何的錯誤訊息(Error message),程式被製成 14.7K 份量的 .dll 式檔案,送進了 Win32Forth 系統,實現了前述兩列直接執行的 WINLIBRARY 及 CALL 指令要求,我得到了正確答案 7。

從上列程式看起來,好像我很不會寫程式,不適合當程式設計師。我承認我的程式很不漂亮,很不規矩,很不標準,很不像樣,簡直就在亂搞,不像是程式。可是,都執行出了我自己想要的結果。這些程式跟高手的作品比較起來,我確實只能算是一個調皮的程式實踐者。這些東西我都沒有打算要賣錢,搞得那麼漂亮幹什麼?為了解決問題我才設計程式,不是嗎?只要能解決問題,我為什麼一定要守那些不必要的規矩?所以四列就是四列,不必多列。

接下來,我們要探討上述教材,能夠被執行出實質結果的一些關鍵問題,這一部份全都是重要觀念,文章可不是信手拈來隨便寫成的,得到確切的結論前,當然也必須實現遠多於需要的研究與測試。

四列 C 例程式, API 的名稱為 subb ,按 C 規矩來解釋:

執行 subb 前,系統堆疊上要先準備好兩個整數,一個是 int x ,另一個是 int y ,它們被包括在小括弧中,注意 x 與 y 被宣告的次序, x 在先, y 在後。 subb 功能程式的執行內容,就是被包括在兩個大括弧之間的程式敘述,此例被刻意簡化成只做一件事,就是將 y-x 計算後的整數結果回傳給系統,而且就將其放置在堆疊上。由於整個功能程式 subb 執行後的結果,回傳給系統的資料型態(Data type)為一個整數,因此,在 subb 的名稱前面,需要使用 int 前引。沒了,這就是全部 C 程式的詳細解釋。

我在 ABC FORTH 系統中,設計了屬於 BASIC 式程式的大括弧指令,也做了跟這裡大括弧之意義完全一樣的功能設計,但當初設計 ABC FORTH 前,從未探討過 C 中大括弧的真正意義。從這裡看起來, C 中大括弧的意義,跟我為 BASIC 設計的大括弧意義,完全一樣。換句話說,我個人就有能力設計出一些 C 的編譯程式,但我可以把編譯環境設計得比 C 更人性、更友善。

回到 Win32Forth 的環境,我們實現這份教材時使用了『10 3 CALL subb』一列參數與指令,系統執行時, 10 比 3 先進堆疊,傳到了 C 環境後, 3 卻先傳給x , 10 才隨後傳給 y ,但兩個數字都沒有消耗掉,仍然保留在 FORTH 系統的堆疊上,執行完 y-x 後得到了 7 ,這個數字再度被擠進 FORTH 的堆疊,才結束程式。所以, FORTH 系統執行完這一列程式後,堆疊上有 3 個數字: 10、3、7 ,這就是全部外顯的執行結果。

我設計的教材,指令執行前,需要兩個整數,而且故意用減法,兩種系統,在堆疊參數的傳遞上,誰先?誰後?的不同效果,才能顯現出來。程式雖簡單,卻代表了一切,兩個大括弧間的程式敘述內容想寫進多少?那是專搞 C 的人才需要傷腦筋的事情,與 FORTH 無關,所以您不必學,我也不必教。想設計出許多個不同的功能程式,全部放在同一個 mydll.dll 程式中備用,依上例增建與 subb 功能程式同樣的結構,就能辦到,這也不是我該教的問題。您有沒有想過?用這種方法可以全用 C 程式提供 FORTH 系統檔案界面功能、浮點計算功能、繪圖界面功能……等等,這也不是我想教的課題。全力以赴,都能辦到,但界面並不單純,例如:浮點數算完了還是得顯示出來,就又非用 FORTH 不可了,所以沒有人用 C 這樣子為 FORTH 系統設計程式。

至於 FORTH 與 C 兩個系統間的參數傳遞格式,我們只需就 FORTH 這方面提示一下就夠了。浮點數也能傳遞,但您要先搞清楚格式,兩者必須相同。如果傳遞的是記憶體位址,那麼,在 C 環境中使用的是絕對位址,在 Win32Forth 系統中使用的是相對位址,二者可執行 rel>abs 或 abs>rel 指令來換算。如果傳遞的是字串時,在 Win32Forth 系統中使用特殊的『z”………”』指令,來進行包裹。由於這種技術並非標準用法,而且我也不認為再過多少年後,這種使用技術還能永遠存在,所以這些說明,只宜作為應景教材。

※以下係關於 C 系統方面的操作細節,與 FORTH 無關。

使用 C 的系統來產生我們所需要的 .DLL 檔案,方法不是固定的只有一種,因為就 C 的立場而言,全世界所有供應 C 系統的公司,也未將此事統一起來。曾經較為流行的 C++ 或 C# ,所安排出來的作法,是在源程式緊接著前處理指引宣告之後,增加一列額外的宣告:

extern “C” {………………}

然後在功能程式(Function)的前引,再增加另一個宣告指令,例如:

_declspec(dllexpot) FUNCTION-NAME() {………………}

如此設計程式, C 系統才能編譯出您想要的 .DLL 程式來。也許其他的 C 系統還有其他的設計格式,但都只能從研讀發行 C 系統公司所提供的使用說明書中,才能得知詳細的使用方法,沒用到前沒有先行學習的價值。

這樣的用法,有一個很大的缺點,想造 .DLL 程式的人,必須取得 C 的源程式,然後明確的按照要求,如同上述的方式,在源程式的相關位置,填入上述宣告或指令。必須有源程式,還必須修改,系統程式可能還得花錢買,隨後還得進行特殊的編譯操作,才能得到可用的結果,以這種方式實現自行建造 .DLL 的整套方法,確實不太好。因此,我僅只保留收集所得的相關資料,而不進入那種環境去研究發展。

評估現況之後,覺得使用公益軟體,來了解這項 FORTH 與 C 間的初步性能,可能比較理想,於是先從 Linux 中的 gcc 開始發展, gcc 建 .DLL 的方式有一個很方便的特點,就是完全不用修改基本的 C 源程式內容,而只需在操作 gcc 的編譯過程中,使用指定操作命令按步驟操作,使用者只須集中注意力於編譯操作上。

C 的範例源程式並不難建立,但有經驗的使用者,建議使用只產生素文字的編輯器,設計 C 的源程式,在微軟作業系統中指的是記事本(NotePad)而非文件檔案編輯器(WordPad),以避免文字控制碼夾雜在源程式中,妨礙後續的編譯操作。

此前,我曾使用過此 Linux 系統中的 gcc ,編譯出 gForth 及丁陳老師傳給我的 eForth in C 源程式,所以系統值得信賴。根據收集到的資料顯示,以 gcc 產生 .DLL 檔案,只有兩次的操作,例如:源程式的檔案名稱假設為 mydll.c ,則先用此源程式編譯出一個物件檔案(Object file),操作方式為:

gcc –c mydll.c

操作完成後,可以 ls 操作命令檢查資料夾內的檔案,就會增加了一個名為 mydll.o 的物件檔。接下來,要用這個物件檔,直接產生一個外界可以使用的動態連結檔,也就是我們想要的 .DLL 檔案。操作時要用到一個意義上為外界可以共享(Shared)的操作命令,操作方式為:

gcc –shared -o mydll.dll mydll.o
如此便完成了我們想要的 mydll.dll 共享式庫存程式檔案,這兩個操作程序,算是已經被我濃縮到了最簡單的程度。

隨後,我利用 E-mail 傳送信件時夾帶檔案的方式,發了一封自己傳給自己的信件,再到另外一台電腦上取出此 mydll.dll 檔案,放置在 Win32Forth V4.2 系統的資料夾內,立刻以上述指令直接操作測試,不幸失敗了,失敗現象是一執行到叫用的功能程式時,整個視窗就立刻消失了,因不留痕跡,無法除錯。

檢討問題時,直覺想到也許就是因為作業系統完全不同,所以不能這樣用。於是在網路上下載了一個 cygwin 的系統,它是一個在微軟作業系統中模擬 unix 作業系統的設計,提供相同功能的 gcc ,我在這個系統中重新做了許多次與 Linux 中完全相同的操作,並將結果直接送進 Win32Forth V6.14 測試,仍然失敗了,失敗現象比前一狀況好了一些,視窗沒有消失,畫面上顯示錯誤訊息,告知找不到一個稱為 cygwin1.dll 的相關庫存程式,於是,我從下載的系統 bin 資料夾內複製了這個程式,併同 mydll.dll 一起放進 Win32Forth V6.14系 統的資料夾內,再度進行同樣的操作,仍然失敗,視窗畫面也消失了。

最後,我仔細思考問題所在,並從網路上印出了一篇 Tom Zimmer 於 1997-12-04 寫給 Thomas 的網路論壇公開信,信中強調, Win32Forth 當初設計時,考慮到微軟作業系統提供的動態連結程式庫,佔用了記憶體的固定位址,任何程式不得侵犯,否則就不能在微軟的作業系統中執行,因此, Win32Forth 系統中的程式碼改採以相對位址的方式被執行,速度會慢一點,但此系統便可以被彈性的安排在記憶體的任何位置來執行。信中也提到,凡想被 Win32Forth 系統叫用的 .DLL 程式,被叫用而進入系統時,絕對不可以使用與微軟作業系統衝突的記憶體位置。關鍵性的三句話是:

1. WindowsNT allows an absolute program in the area between 0x400000 and about 0x600000.
windows95 I believe places programs well below this.
So if you are specifying an absolute program address below 0x400000, then it won’t run under WindowsNT.
2. You have to be careful to not allocate the same absolute address to two Dlls.
3. This is basically why I decided to make Win32Forth use relative addressing. It is slower, but more flexible.

因此,我直覺上知道前述的兩個 gcc 都產製了與微軟作業系統衝突的 .DLL 檔案。要解決這個問題,只能找合乎規格的 C 編譯系統來用,因為只是試用,不值得花錢買任何系統,但公益系統必須是藉由微軟的基礎開發出來的系統,才會考慮產生的 .DLL 檔案程式,執行時不與微軟作業系統衝突的問題,我缺乏這方面的概念,只好找女婿幫忙,他很快的從網路上找到了我需要的東西,就是 MinGW 系統,裡面也有 gcc ,但要到 DOS 的原始視窗中去執行,執行的結果可以與 gcc 暫時同放置在 MinGW 的 bin 資料夾內,要使用時,才將 mydll.dll 檔案移到希望放置的資料夾中去。

做好了這樣的安排,一次便完成了希望的測試,得到了很正確的結果,證明了根據 Tom Zimmer 信中所言而思考的方向是對的。有了結果,我便有機會進行精簡範例程式的發展,最後,將寫成這篇教材所需要的範例程式,精簡成了只剩四列。

妥善操作 gcc 所需用到的整體操作命令選項(Overall options),是能夠完成自行產製 .DLL 檔案的最重要關鍵,有關整體操作命令選項的使用資料非常多,而且不容易建立概念,想熟悉它們,最好的方式,還是從網路上參考別人完成的實例,並練習實作後,體會操作結果所表示的意義,才能逐漸的熟悉起來,做這件事,必須在學習 C 程式設計方面下許多功夫,操作這些東西,完全沒有學問,所以我也很不想學。

講完了這些技術,並不表示一切就到此為止,您如果好學,在 FORTH 的領域,完全可以學通這種兩個程式語言間的橋接技術, FORTH 系統是完全透通的,使用 SEE 指令,可以直接追蹤這些指令是如何設計而成的,看懂了程式與系統的意義,您也可以在自己的環境中依樣設計一個,除了 FORTH ,這個世界目前還沒有任何其他程式語言,能夠提供如此健全的功能給使用者。

如果大家都從小就開始學 FORTH 程式語言,會有許多好處,它不僅只是可以供您寫程式解問題而已,它還提供了完全可以學得通的一切與電腦有關之哲理。如果您也像我一樣使用了 FORTH 已經 30 幾年,能夠像我一樣將 FORTH 系統執行的原理寫成文章,那麼,上述文章中大家可能會認為是我在講笑話的事情,就不再會被認為是笑話了。

我可以不去追蹤本文所論述的這項技術所牽扯到的指令,單憑自己的想法,設計出另一套由 FORTH 去叫用其他程式語言功能程式的指令來,問題只在於需不需要一直發展這樣的程式?我總認為別的程式語言最後都會死去,而永遠都可以存在的 FORTH ,只能在別的程式語言還活著的時候,跟它們連一連,等它們都死掉了,連在一起也就沒有任何用處了,到頭來還是只剩下 FORTH 。這幾十年來的日子, FORTH 是怎麼過的?好像是千山它都獨行,請不必相送。

早期的系統駐留功能(Resident 例如:side kick用法),我用幾個指令就能完成設計,只要不搞破壞,讓兩個系統可以串來串去,不也是互相叫用功能程式嗎?我們曾用這樣的方式請 BASIC 幫忙算浮點計算,也讓 FORTH 幫忙 BASIC 執行一些程式,將結果放進 BASIC 固定的參數堆放位置。

我親自設計過好幾次多工程式(Multi tasking),用得漂亮時,只控管一個使用者指標 UP(User Pointer)的技巧,就能讓原 FORTH 系統完全停頓下來,轉執行其他任意的別種功能程式,別的功能程式結束了,仍然只依靠那個被控管的 UP 指標值,再執行很少的低階快速指令,就能恢復整個 FORTH 系統的繼續執行。 使用同樣的技巧,當然也能讓 FORTH 去執行 .DLL 內的功能程式。本文所探討的 FORTH 指令,大部份都在執行功能程式名稱的處理,接受 CALL 的後續輸入字串後,到 .DLL 的檔案內去尋找這個功能程式名稱所在的位置,找到了就取得這個功能程式該有的起始執行位址,合理控管 UP ,就能在執行完功能程式後,恢復 FORTH 系統的原有正常狀況。我也因此能夠體會出為什麼 Win32Forth 系統在執行完 CALL 功能程式之後,從堆疊傳遞過去的參數,依然還擺在原來的堆疊位置,交還給 FORTH 系統。我講得出這樣的執行機制,我為什麼不能自己也設計出這樣的程式來?

進一步誇口,我已經很熟練於操作我自己設計的檔案讀寫程式了,我能看到所有其他檔案程式的內容,我讀得進別人的標準結構,我當然也設計得出別人所需要的結構,因為工作只是填資料。那麼,前述所說的笑話就不是笑話,單用 FORTH 設計一個可以產生.DLL 程式供別人使用的檔案,當然能夠辦到,這就是產生 .DLL 程式編譯器的設計方法,我不是在講笑話吧?我用 C 辦不到,我用 FORTH 絕對辦得到,現今,別人已經辦到了,想做時,也能拿來參考。

而且,早就有商售 FORTH 系統,在網頁上做廣告,強調可以直接叫用 Visual BASIC 編譯產生的程式,我卻不認為這是甚麼偉大的新技術,也不值得投入太多精力去發展,因為, Visual BASIC 也早就開始逐漸地沒落了。叫用其它程式語言編譯出來的程式之技術,最後的處境都將相同,等到發行公司因無利可圖而不再賣這個系統時,前面的努力,都會成為泡影。為了節省非常有限的 FORTH 人力,這種發展就不必重覆浪費了。

目前, C 式語言還在流行階段,熟悉這些同類程式語言的讀者,可以參考本章的內容,依樣畫葫蘆,滿足一下新奇感就夠了,我並不鼓勵長期這樣子發展系統,因為產生的結果,就像 C 式程式語言的本質一樣,系統本身是完全不透通的,對技術的傳承,完全沒有幫助。我也希望,此後永遠不再需要使用 C 來寫程式。


2026年4月15日 星期三

邏輯運算(Logic operation)

邏輯運算(Logic operation)


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


本文所探討的邏輯運算,為『條件整合』所需的邏輯運算,而非『位元操作』方面使用的邏輯運算。

為了讓ABC FORTH數學計算系統增添這種邏輯運算的功能,我才慎重其事的重新探討此一主題。討論的內容仍然憑藉著邏輯運算的傳統原始定義,但討論的結果,可以產生創新的作法,並實際實現將系統中的邏輯運算指令,設計成以適當的中文意思來表達,且令其在程式中的使用方式,符合中文的語法規格。

大體而言,數學計算系統中的處理對象,若能夠被明確的劃分成各個獨立模組,系統便容易規劃、設計、發展。舉例而言,ABC FORTH數學計算系統,將整數、浮點數、複數…等數字處理對象,都當作各自獨立的模組來考慮,模組化後可以大大的簡化整個系統,讓各模組的問題,只須在各模組的範圍內自行解決。

因此,在ABC FORTH系統中,欲添加邏輯運算的功能時,最佳的作法,當然也就是繼續將這項主題,考慮成一個新建的單一模組來設計程式,讓邏輯運算的位階等同於整數運算、或浮點數運算、或複數運算,是一個獨立的新模組。

事實上,邏輯運算在實際的科學應用中,原本就與各種數字的運算不相干。數字運算計算3乘2得到6,1加1得到2,配合上其他所有的運算,形成一套完整的數學體系。邏輯運算執行0與1的各種運算後,也會得到一些運算結果,但這裡的0與1不表數字,而是表真或假、對或錯、是或非、有或無、開或關、高或低…等等所謂的邏輯相對狀態,0與1僅只是借自數字系統的兩個元素當作符號,這個1也可以使用除了0以外的其它任何數字來代表,但都與數字的大小或正負意義完全無關。總而言之,邏輯運算原本就與各種數字運算無關。

邏輯學是研究理論數學、應用數學、高深數學之基礎工具,也一直都是電腦科技發展的基石,電子邏輯電路工程更是經常需要用到邏輯運算設計產品。數學計算系統不應排除邏輯運算的功能,它係電腦以執行軟體程式的方式,實踐邏輯學問,沒有邏輯運算功能的系統,性能便有所欠缺。因此,將全部的邏輯運算功能建立在數學計算系統中,是有必要的,健全的邏輯運算功能,也有助於處理人工智慧方面的程式。我將加建邏輯運算功能的方法搞成極其簡明的方式,當然也就有助於各方面的應用發展。

2010年七月,我才開始設想『如何增建處理邏輯運算的功能進入系統?』的問題,自覺很容易實現這樣的設計。但是,那時只是初步的構思,只考慮仿照一般傳統程式語言的習慣,暫時僅融入『而且』(AND)與『或者』(OR)兩個單純指令。這篇文章則進行廣泛性的探討,包括如何給予適當的邏輯算子中文名稱?我個人認為,應該研究過它們在系統程式語言中之表達位置後,方給予適當的命名,才會比較恰當。讓指令的名稱符合語言上下文的啣接要求,這樣才能有助於設計出具有優良語法的程式系統。

一般而言,若只討論邏輯運算不同的名稱,基本邏輯運算的算子共有六個,它們是:

(1) AND (2) OR (3) XOR (4) NAND (5) NOR (6) NOT。

在FORTH程式語言中,邏輯算子(Operator)所需要的算元(Operand)操作對象,除了NOT只需要先行提供一個算元外,其他五個,都需要先將兩個算元操作對象放在堆疊上,然後才能執行邏輯運算。執行的結果都只得到唯一的狀態表示值,並將其留在堆疊上,供後續指令使用。換句話說,FORTH的邏輯運算,是一種後算符式的運算。1994年訂定的ANSI FORTH指令標準中,只將前三個當作標準基本指令,後三個可以導得,設計如下:


 
: NOT	 0=		;
: NAND AND NOT ;
: NOR OR NOT ;


如果單純的以1表示『真』,以0表示『假』,來製作一份邏輯運算真假值表,NOT不製表,原為1則得0,原為0則得1,其它的邏輯運算真假值表示如下:


上述全屬邏輯運算的原始性質定義,我們透過它來討論問題。

學過電子學上邏輯電路設計技術的人,一定知道,只要根據單一種邏輯閘,NAND或NOR僅擇其一,把它當作萬用基本邏輯閘,就可以設計出另外五種邏輯閘的知識。目前,市面上販售所謂的以NAND或以 NOR單種電路製造的快閃記憶體,大概也就是依據這個道理設計而成的。

事實上,在FORTH程式語言中,我們單純的只以軟體程式來設計,也一樣可以完成這種方式的設計,只不過是我們另得配合使用簡單的堆疊操作指令來達到目的。

我們若只以NAND當萬用基本指令來設計時,其它的五個是:


 
: NOT	( n – n ) 	  	DUP NAND ;
: OR ( n1 n2 – n ) DUP NAND SWAP DUP NAND NAND ;
: AND ( n1 n2 – n ) NAND DUP NAND ;
: NOR ( n1 n2 – n ) DUP NAND SWAP DUP NAND NAND DUP NAND ;
: XOR ( n1 n2 – n ) OVER OVER DUP NAND NAND –ROT SWAP DUP
NAND NAND NAND ;

我們若只以NOR當萬用基本指令來設計時,其它的五個是:


 
: NOT 	( n – n ) 		DUP NOR ;
: OR ( n1 n2 – n ) NOR DUP NOR ;
: AND ( n1 n2 – n ) DUP NOR SWAP DUP NOR NOR ;
: NAND ( n1 n2 – n ) DUP NOR SWAP DUP NOR NOR DUP NOR ;
: XOR ( n1 n2 - n ) OVER OVER DUP NOR NOR -ROT SWAP DUP
NOR NOR NOR DUP NOR ;


這些FORTH指令簡單易懂,是初學者很好的程式設計練習指令,而且上述設計方法不是唯一解答,您若有興趣試一試自己的本領,不妨就自己另搞一套,並自行摸出如何證明自己的設計絕對正確的辦法。這些整理出來的結果,也是當初發展出eforth系統前,曾經被仔細探討過的題材,是想要將FORTH反璞歸真前的一個重要程序。

有了這些根據,我們可以開始探討如何將這些功能全部加進ABC FORTH數學計算系統的問題了。在這個系統中,若以BASIC式的方式來設計程式,則『條件整合』用的邏輯運算,只會出現在條件分支指令組IF…THEN之間,一個典型的『而且』(AND)指令使用範例如下:

100 IF ( A > 5 ) AND ( B > 5 ) THEN 700

其中,( A > 5 ) 與( B > 5 )是兩個範例條件,本文只使用它們來代表一切可能的條件,是假設性的算元,AND便對這兩個條件進行邏輯運算,所得結果用來決定是否分支到THEN後面所指示的前往位置700去?改成中文程式則為:

100 如果 ( A > 5 ) 而且 ( B > 5 ) 前往 700

這樣的程式書寫方式,幾乎與我們中國人的語言表達方式完全一致了。有這麼重要的意義在其中,就更值得為其它另外五個邏輯運算指令,好好的設計出適當使用規則。另外一個『或者』(OR)指令,性質與『而且』(AND)相當,就照上述安排,成為標準的中算符指令使用方式,非常理想,由英文譯成中文的指令名稱也很恰當,不須要再進行任何修正。

這兩個指令,還有一項特點,也就是可以連續使用無限次於IF…THEN之間,用幾次都不成問題。因為每經過一次邏輯運算之後,都只留下一個邏輯狀態值,繼續以上列中算符方式安排在程式中時,便也能繼續與後續的新條件亦即算元操作對象進行邏輯運算,直到不算了為止,仍只得到唯一的結果,供給『前往』(THEN)指令使用。

另外的四個慣常邏輯運算指令,在英文環境的適當表達方式,則與中文有所不同,這是我從上述真假值對照表中看出來的問題。

當然,表達方式可以有很多種,只要使用方式合理,使用者使用時,可以覺得足夠友善,都可以作為設計的依據,而且對我而言,都很容易設計得出來。例如:我可以懶到根本就將所有的邏輯運算當作也是一種函數來考慮,那麼,六個邏輯運算,在程式中,就可以全部統一成前算符方式,使用於程式設計中。

我在ABC FORTH系統整數體系內所設計之『位元操作』應用方式的邏輯運算,就是前算符的格式,以便讓這六個指令可以呈現函數表示式的性質,如果在IF……THEN間的『條件整合』情況,繼續延用前算符規矩,程式就必須寫成下列格式:


 
AND 	( ( A > 5 )  ,  ( B > 5 ) )
OR ( ( A > 5 ) , ( B > 5 ) )
XOR ( ( A > 5 ) , ( B > 5 ) )
NAND ( ( A > 5 ) , ( B > 5 ) )
NOR ( ( A > 5 ) , ( B > 5 ) )
NOT ( A > 5 )


系統將這些函數式的邏輯算子執行完畢之後,都只得到一個唯一的狀態值,當然就能提供後續的THEN指令使用了。可是,這樣子使用之後,就完全失去了與人類語言直接對應的關係,結果就等同於傳統FORTH中的後算符使用方式,這兩種類型都與人類慣常語言完全不匹配。

另外還有一項因素,上述AND與OR兩個較完美中算符式的設計,也是一般傳統程式語言中所採用的格式,若不跟著以同樣的方式使用,想轉譯舊程式進入ABC FORTH系統時,就會產生困擾。因此,必須保留這兩個邏輯算子,成中算符的格式來使用。

其它的邏輯算子就不是這樣了,如果生平根本就不用英文思考問題,不必管它XOR、NAND、NOR、 NOT的實質意義為何?把這些算子都譯成『與…進行某種邏輯運算』,那麼,前三個指令就設計成也是中算符就很理想,例如:

( A > 5 ) XOR ( B > 5 )

表示條件( A > 5 ) 與條件( B > 5 )進行XOR邏輯運算

至於NOT,則只好維持原來FORTH後算符的方式了,我在ABC FORTH系統中,就將英文的邏輯運算指令,設計成這樣的使用格式,但深覺它忽略了中文,不尊重我們中國人的存在。

考慮在中文程式中使用這些邏輯運算指令時,若純用中文進行思考,便能夠深刻的體會出指令名稱、實質意義,與語言規格間的重要關聯。為顧全大局,要讓這四個指令都必須以『後算符』的格式來使用,才比較恰當。為什麼?請回頭仔細看看邏輯運算真假值對照表中顯示的各種狀態,您就可以明白其中的道理。

我在台灣讀過的書本中,幾乎所有與邏輯算子有關的譯名,大約都被固定的翻譯成非常怪異難懂的名稱了。『XOR』被譯成『互斥或』,『NAND』被譯成『反且』,『NOR』被譯成『反或』,『NOT』大概被譯成『非』吧。如果要拿這樣的譯名來設計中文程式,那我寧可不設計程式了,不信請讀一讀下列用法,看看您能不能直接讀出程式的意義來?

100 如果 ( A > 5 ) 互斥或 ( B > 5 ) 前往 700

講實在話,我讀不出來。

於是,我為這四個邏輯運算指令想好了比較一致、比較恰當的中文名稱,將它們都設計成後算符的使用格式,並且在兩個條件算元操作對象之間,添加一個逗號『 , 』當作分隔符號,完成了設計。此處再度複製一份真假值表,以便就近參考,請先行觀察四個指令在表中呈現的意義,然後再看看分別被譯成的適當中文名稱,對照如下:



 
XOR		一是一非
NAND 不為皆是
NOR 兩者皆非
NOT 狀態相反


我在ABC FORTH系統中實現了這樣的設計,它們的使用規格則如下列:


 
100 如果 ( A > 5 ) , ( B > 5 ) 一是一非 前往 700
100 如果 ( A > 5 ) , ( B > 5 ) 不為皆是 前往 700
100 如果 ( A > 5 ) , ( B > 5 ) 兩者皆非 前往 700
100 如果 ( A > 5 ) 狀態相反 前往 700


如此一來,這些邏輯運算就都符合我們的中文語法了,而且想進行連續邏輯運算的程式設計時,使用小括弧,清楚的將各個條件算元操作對象,按同樣格式區分開來,也能完成沒有次數限制的邏輯運算。以前,從未有人這樣子翻譯這四個邏輯算子的中文名稱,我現在則以邏輯算子在中文話語中的上下文關係,作為考慮基礎,譯名也包括了中文的直覺意義,可以通順的使用於程式語言中,這樣做算是創舉。

我在習慣於快速中文打字的操作後,已經不再認為:給予指令四個字,例如:『狀態相反』,會比只給單個字,例如:『非』,的譯名要差了。好的指令中文譯名,應該是清楚第一,通順至上。在以打字方式設計程式的作業環境中,字有幾筆、幾劃?更不重要,今人考慮的問題,早已與前人大不相同。

邏輯運算的功能,完全能應用在任何需要邏輯運算的場合,包括設計電子邏輯電路時的應用,使用者可以在未接觸硬體前,先以執行軟體程式的方式來實現。但是這種邏輯運算,屬於『位元操作』應用方式的邏輯運算,並非『條件整合』應用方式的邏輯運算。因此,在ABC FORTH數學計算系統中,就必須使用整數體系內的六個函數式的邏輯算子來設計程式,有別於此處探討的邏輯算子。

邏輯運算是數學運算中的一門分支,本文僅只處理了六種慣用邏輯算子,如果另有新的邏輯算子產生,隨時可以迎合要求,將其加入這個完全按照自己的理念設計出來的ABC FORTH數學計算系統。

我可以不堅持自己的譯名就是最好的選擇,大家若有更高明的見解,也可以經過討論後,重新設計進系統,這就是不必受制於人的好處,我們隨時辦得到。

這樣子的研究,純粹衝著我們中國人自己的文化而來,打從幾十年前開始學習電腦程式語言起,我心中便對中國人想使用電腦設計程式時,始終受制於西方文化設計出來的規範,感到忿忿不平,如果自己完全沒有能力在這方面進行改善,那就不必空談,只能拈香跟拜(台語,表示只能忍受一切羞辱,任人擺佈的意思)。

整理出這樣的研究,它的好處在那裡?完全無利可圖,所以何必曰利?但是,如果大家都不願正視這樣的研究,那麼,只好一輩子永遠拈香跟拜。

現在不一樣了,我早已習慣於在自己的電腦上大肆使用中文,譬如打文章、寫信、作記錄、看資料、看新聞、看程式…等等,我也有能力設計出可以完全只使用中文來當指令的程式語言,那麼,為什麼不更進一步的發展?也許我的個人研究不太成熟、不夠完美、不算甚麼,但是,至少我已經能夠這樣做了,上述的中文程式語言功能,我確確實實的實現了!而且,我會繼續思考,繼續努力,繼續設計,在數學計算系統方面,別人能做甚麼,我也絕對能設計得出對等功能的系統,性能還可以中文為主,並且直接超越別人的設計!

與數學計算相關的大部份實際程式中,必須使用到『條件整合』式邏輯算子的情況非常簡單,幾乎只要準備AND與OR的功能就夠了。在進行專家系統方面的程式設計時,反而更需要全部邏輯運算的功能,尤其是如果想用中文來設計專家系統時,除了要讓執行結果絕對符合專家資格,不出差錯外,對於日後的程式維護,也要講求精簡明確,那麼,這一篇文章內所強調的要點,就會很有價值。

2026年4月2日 星期四

常微方程式之 Runge-Kutta 解法

常微方程式之 Runge-Kutta 解法


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


本文專注於引用單一文獻,將一個標準的 FORTRAN 程式,直接改寫成 ABC FORTH 數學計算系統可以執行的程式。改寫的方式幾乎是一列一列對應式的翻譯,執行解得的答案必須與原資料文獻一致,操作的便利性必須符合現今時代潮流,內容則是一個實用的數值分析標準範例。

參考文獻源自:

Brice Carnahan, H. A. Luther, James O. Wilkes, “Applied Numerical Methods”, Chapter 6. Example 6.3. p.367 ~ p.380,

“Forth-order Runge-Kutta method - transient behaviour of a resonant circuit”

本文的主要目的,並不想詳細講解,如何使用計算機設計程式來求解常微方程式?也不想詳細介紹所謂的 Runge-Kutta method ,數值分析專門書籍內可以找到這方面的知識,上述文獻就是非常完整的標準教材。重點集中於強調:如何將任何 FORTRAN 數值分析程式改寫成 ABC FORTH 程式?並展示,大型、複雜、實用的 FORTRAN 數值分析程式很容易改寫,所得的程式,比原 FORTRAN 程式使用起來更為方便。

過去幾十年,普天之下,已不知有多少的專家學者,發表過難以計數卻都非常有用的 FORTRAN 數值分析程式。電腦科技的演進,如果會廢棄這些先聖先賢費盡心血提供出來的研究成果,那就實在是人類的悲哀了,很不幸的是,目前電腦軟體技術的走向,傾向於如此。當年,許多聖賢,公開電腦技術,研究解決數學問題所做的貢獻,都寫成程式、發表成論文,有許多都算得上是精彩的博士論文。但是,現今的電腦環境,幾乎已經不容易再去執行那些有用的程式了。本文單舉一個例子,以 ABC FORTH 數學計算系統實現這種功能。幾十年來的專家學者,在數值分析實務上所做過的貢獻,完全可以輕易的使用 ABC FORTH 數學計算系統令其重現,並且化腐朽為神奇。

本文的主題,大約是理工科系大學三年級以上的學生,才可能遇到的特別數學問題。如果 ABC FORTH 數學計算系統,能夠解決這一等級的數值分析問題,那麼處理一般性的數學計算問題就當然是輕而易舉。已經擁有 ABC FORTH 數學計算系統的使用者,請耐心的讀完本文,以這個實例學會譯寫 FORTRAN 程式,這是本章的宗旨。

常微方程如何用程式來求得解答?

大學中的常微方程課程,教材內容偏重於理論數學,專挑一些理想化了的典型常微方程,例如:線性、常數係數、低階、規則型態的方程式來解釋分析,最重要的是,要能手解求得存在的唯一解答,如此才能讓學生建立常微方程式所代表的觀念,各個理工科系的學生,各有應該專注的不同型態之常微方程,各自深入了解後,就可以應用到後續所學的專業科目上去,能用手解得到的答案,有一個專門術語,稱作解析解(Analytical solution),也就是透過數學理論解釋分析後歸納出來的答案,健全完整,考試要考。

理論上的手解方法,通常都已固定落實了,甚至於都可以寫成公式直接套用。通常的解法,是以固定的演算程序,寫出常微方程之係數尚未確定前的通解(General solution),然後,將已知的起始條件(Initial condition)或邊界條件(Boundary condition)代入通解,最後,確定通解中各項式的未定常數係數,便能獲得代表符合所有條件的特解(Special solution)。

理工科系的學生,要用這些解答來探討他所研究學習的專業科目問題,例如:機械工程系的學生,解得流體力學或熱傳導學的常微方程答案後,就可以了解系統內物理現象變化的趨勢。電機工程系的學生,解得電磁學的常微方程答案後,就容易明白電磁現象的變化。土木工程系的學生,也按同樣的方式,就能夠了解建築結構上力的分佈。化學工程系的學生,用同法來了解化學反應變化。換句話說,工程科技,一定要學常微方程,甚至於偏微方程。

上述僅用一段話,說明求解常微方程的目的,而常微方程通常都是表達自然現象的一種數學式子,能夠被手解的類型所佔比例是非常少的,科學家要大刀闊斧的合理簡化式子後,才能讓原來表示自然現象的常微方程變成可解析,這就是科學上常說的大膽假設。

現實世界中,如果學過了某門工程科學,日後工作時遇到了實際的常微方程,不能每次都用大膽假設法解問題。從事於工程工作是負有責任的,不能全憑假設解問題;較無責任問題的研究工作,有時也不搞假設,因為如果假設不成就會長期沒有進展,研究停滯不前。不能手算獲得解析解的常微方程,不一定就無法可解,科技發展到今天,有了電腦可以協助人類進行快速數學計算,產生了所謂的數值分析技術,以分析問題後採用有依據的數值計算方法,用逼近答案的演算方式求得解答,其結果同樣可以讓人類了解自然現象變化的趨勢而解決科學問題,這樣的解題方法,在學校兩小時的考場內,考試沒辦法考。

求解常微方程的答案時,使用數值分析法與使用手算推演解析解法完全不同,就比較二者在得到答案程序的差異上而言,甚至於可以說是採用完全相反的步驟與方式來獲得解答的。數值分析法一開始,就得使用唯一已經確定的起始條件點或邊界條件點,做為起算基點。古時候,有很多的聖賢應用數學家,例如:本文採用的 Runge-Kutta method 即為其中之一,研究出各種逐步演算推進到下一點的合理方法,寫成數學通式,再翻譯成電腦可以執行的計算程式,藉由電腦的快速演算能力,逐點計算,逐點推進,便可算出此後所有合乎常微方程式所有條件的答案值來。

上述兩種解法所得的答案,最大的不同點,顯示在答案的表示方式上。解析解的答案通常都是可以寫成漂漂亮亮的數學函數的解答,所有工程科系的學生都習慣見到這樣的解答。數值分析解法所獲得的答案,見不到函數表示式,僅只有好幾筆一序列長串印出的數目字,乍看之下,實在不容易看出個所以然來。然而,在應用上,兩種答案意義是一樣的,而且理論上必須相等。這就好比您在查函數表的數值時,您也看不到函數表示式一般,但可以立刻用來解決工程問題。

數值分析技術一定要用到『函數曲線繪圖程式』,利用繪製函數曲線圖形的程式,就可以用圖形來顯示上一段敘述內所得到的數目字解答,將答案繪製成圖後,您就能夠完全了解,數值分析法解得答案的意義了。

以前大學教育裡不太教這種解題技術,當時,電腦的運用沒有那麼方便,情有可原,現在的大學應該要重視這方面的教材,因為,這是一種實際的解題方法,正式的工程問題就得使用。您用得好,工程或研究就做得好,您用不好,工程或研究就做不好。只會解析解的工程科系學生,考試可能可以考得高分,稱得上是秀才。但如果秀才抗拒電腦,拒絕學會數值分析法解工程或研究問題,秀才就會經常沒有用。兩種方法都搞得很好,才能算是人才,我們的社會需要人才而不是秀才。

本文採用的大型例題,要完全研究透徹,須要仔細讀完大約五十頁的英文原文著述,是數值分析教材中一章的份量,若要全譯成漂亮的中文,就得大費周章。我創作 ABC FORTH 系統的主旨不在這裡,我就是希望今後這種文章都不必寫了,只要忠實的告訴您,題材的原始出處,您自行參考,直接取來運用,為後代子孫著想,節約人類寶貴的資源,才是主要宗旨。

這個例題是電路學上,一個分析電阻、電容、電感(RCL)三個電子元件,串聯成一封閉迴路所形成的振盪電路,它可以寫成一個二次的常微方程,來表示電壓隨時間變化的關係,希望能解得具有起始條件後電壓隨時間暫態變化的趨勢解答。


起始條件係假設迴路原為開路狀態,瞬間在電容兩端充以 10 伏特電壓,並立刻將開路改成閉路,則電容兩端電壓隨時間變化的二次常微方程表示式,及這樣的初始條件可以表示式為:

LC*(d(dV/dt)/dt)+RC*(dV/dt)+V=0
V(0) = 10
dV(0)/dt = 0

三個電路元件的物理量假設為:
電容值固定為 C = 2 * 10 ^ -6 farads
電感值固定為 L = 0.5 henries (程式中被表示為 HL )
電阻值可選擇為 R = 0, 100, 1000, 1500 ohms
電容兩端瞬間起始電壓固定為 10 volts
分析時步進間隔值分採 H = 0.00001, 0.0001, 0.001, 0.002, 0.005, 0.01 sec
分析最長時間設定成小於 0.02 秒,亦即 TMAX < 0.02 sec

這個典型的例題是屬於一個可以手算求得解析解的問題,因此,解析解也被用在程式中直接計算,以便將所得結果,與使用 Runge-Kutta method 數值分析解法所得的答案,進行直接比對,驗證數值分析法的合理有效。

詳細的分析與討論,本文不再贅述,有興趣的讀者請自行參考文獻。

這個程式可以被應用到求解所有的常微方程上去,包括高於二次以上的高次常微方程,前處理分析出來的相關通式改寫成程式後,被安排在 MAIN 指令的第 120 及 130 兩列中,想應用於別的常微方程式問題時,就得在這裡替換通式表示式,詳細過程仍請參考文獻後仿照應用。

我可以把這個程式寫得更好、更有效率、更有通用性,但達不到教大家直接改寫 FORTRAN 程式進入 ABC FORTH 系統的效果。因此,我以最大的耐心,逐列翻譯。除了前文曾經提及的輸出、輸入全自創,而不改寫外,整個程式幾乎沒有需要大肆更動之處,簡單的程度,就好比將四川話翻譯成普通話那麼簡單。 FORTRAN 裡面有一個可以多重分支,GO TO (1,2,3,4,5), M 的指令,也很容易依理改寫。程式改寫完畢後的執行結果,當然要與原文列印的結果相同,最多僅能有小數點後面少數幾位數的系統計算性能誤差。這些基本要求, ABC FORTH 都一一實現了,而可以迅速的操作性能,則是 FORTRAN 程式語言所無法辦到的。

微分方程的數值分析解法,其實強調的重點是在試出最恰當的每次步進間隔值,如本文範例程式中的 H ,應該取多少才恰當?取的太大了,逐點推進演算的結果就會算不準。取的太小了,每次可以推進的距離就很小,要算很久、很多點後才有結果。這個現象就如同使用離散(Discrete)表示法,以可隨時間變化的加速度來計算下一個速度值,離散時刻的時間分割至很小時,解答會較準,但要推進很多點後才會有最後希望知道的速度答案。一次就想算得許久時刻後的速度,答案就會很離譜,表示時間間隔值取的太大了,根本不能這樣算。如果重點是這樣,那麼容易操作的數學計算系統就會很有用,可以快速修改、調整、獲得恰當的步進間隔值結果。 ABC FORTH 當然比 FORTRAN 好用多了,這就是我為什麼創作 ABC FORTH 數學計算系統的主因,它可以使運用電腦解決問題的習慣完全改觀。能夠執行數學計算的系統不算甚麼,擁有即時性操作效果的系統就非等閒之輩了。

我以這種方式貢獻給大家的 ABC FORTH 數學計算系統,能使此前出版過的所有數值分析教科書永遠有用,永遠不必被淘汰,因為應用數學的理論部份,是大約 200 年不會過時的,書中的實務範例程式,則可以經由 ABC FORTH 數學計算系統輕易再生,這樣做等於是『化腐朽為神奇』,講求社會道德,減少隨便廢棄有用物質而造福全人類。

我不想浪費時間在電腦編輯系統中,尋找漂亮的數學表示符號編寫這篇文章,所以一些數學表示式,就編寫成意義相同,外觀卻有一點不尋常的格式。

原 FORTRAN 程式去除刊頭文字說明及無用的 FORMAT 內容後,重新打字輸入於此處,數據及執行結果則不再編列於此。改寫完成的 ABC FORTH 對應程式,及其中一套數據輸入後的執行結果,則用電腦系統的可選擇性複製、轉換環境後再貼上的功能,轉置到此處,這是一篇實用教材。


 
   
c     APPLIED NUMERICAL METHODS, EXAMPLE 6.3
c     ELECTRICAL TRANSIENTS USING THE RUNGE-KUTTA METHOD
      IMPLICIT REAL*8( A-H, O-Z)
      INTEGER RUNGE
      DIMMENSION F(2), V(2), IMAGE(1500)
c     …..READ AND PRINT DATA
   1  READ (5,100) R, HL, C, VZERO, H, TMAX, IFREQ, IFPLOT
      WRITE (6,200) R, HL, C, VZERO, H, TMAX, IFREQ, IFPLOT
c     …..INITIALIZE T, V(1), V(2) AND ICOUNT
      T = 0.0
      V(1) = VZERO
      V(2) = 0.0
      ICOUNT = 0
c     …..COMPUTE ALPHSQ AND ALPHA, PRINT HEADINGS
      ALPHSQ = 1./(C*HL) – R* R /(4.0*HL*HL)
      ALPHA = DSQRT (DABX(ALPHSQ))
      RO2L = R/(2.*HL)
      IF ( IFPLOT .NE. 1 ) GO TO 3
        CALL PLOT1( 0, J, 11, 6, 19 )
        CALL PLOT2( IMAGE, TMAX, 0., DABS(VZERO), -DABS(VZERO) )
    3 WRITE (6,201)
c      …..IS CIRCUIT OVER-, CRITICALLY-, OR UNDER-DAMPED
c      …..COMPUTE ANALYTICAL SOLUTION, PRINT AND PLOT
    4  IF (ALPHSQ) 5, 6, 7
    5  TRUV =VZERO*DEXP(-RO2L*T)*((1.+RO2L/ALPHA)*
    1 DEXP(ALPHA*T)+(1.-RO2L/ALPFA)*DEXP(-ALPHA*T))/2.0
      GO TO 8
    6 TRUV =VZERO*DEXP(-RO2L*T)*(1.+RO2L*T)
      GO TO 8
    7 TRUV=VZERO*DEXP(-RO2L*T)*DCOS(ALPHA*T-DATAN(RO2L/ALP
    1 HA))/(ALPHA*DSQRT(C*HL))
    8 IF ( IFPLOT .NE. 1 ) GO TO 10
      CALL PLOT3( 1H*, T, V(1), 1, 8)
   10 WRITE (6,202) T, V(1), TRUV, V(2)
c     …..CALL ON THE FOURTH-ORDER RUNGE-KUTTA FUNCTION
   11 K = RUNGE(2,V, F, T, H )
c     …..WHENEVER K=1, COMPUTE DERIVATIVE VALUES
      IF ( K .NE. 1 ) GO TO 13
      F(1)=V(2)
      F(2)=-R*V(2)/HL –V(1)/(HL*C)
      GO TO 11
c    …..IF T EXCEEDS TMAX, TERMINATE INTEGRATION
   13 IF ( T .LE. TMAX ) GO TO 16
      IF ( IFPLOT .NE. 1 ) GO TO 1
      WRITE ( 6,203)
      CALL PLOT4( 7, 7HVOLTAGE )
      WRITE ( 6,204)
      GO TO 1
   16 ICOUNT=ICOUNT+1
c     …..PRINT RESULTS OR CALL DIRECTLY ON RUNGE
      IF ( ICOUNT .NE. IFREQ ) GO TO 11
      ICOUNT = 0
      GO TO 4
c     …..FORMATS FOR INPUT AND OUTPUT STATEMENTS
100   FORMAT ( …………..)
200   FORMAT (……………)
201   FORMAT (…全略……)
202   FORMAT (……………)
203   FORMAT (……………)
204   FORMAT (……………)
      END

FUNCTION RUNGE( N, Y, F, X, H )
IMPLICIT REAL*8(A-H, O-Z)
REAL*8 Y, F, X, H
INTEGER RUNGE
DIMENSION PHI(50), SAVEY(50), Y(N), F(N)
DATA   M/0/
M=M+1
GO TO (1,2,3,4,5), M
c     …..PASS 1
   1  RUNGE=1
      RETURN
c     …..PASS 2
   2  DO 22 J = 1 , N
      SAVEY(J)=Y(J)
      PHI(J)=F(J)
    22 Y(J)=SAVEY(J)+0.5*H*F(J)
X=X+0.5*H
RUNGE=1
RETURN
c     …..PASS 3
   3  DO 33 J = 1, N
      PHI(J)=PHI(J)+2.0*F(J)
    33 Y(J)=SAVEY(J)+0.5*H*F(J)
RUNGE=1
RETURN
c     …..PASS 4
   4  DO 44 J=1, N
      PHI(J)=PHI(J)+2.0*F(J)
    44 Y(J)=SAVEY(J)+H*F(J)
X=X+0.5*H
RUNGE=1
RUTURN
c     …..PASS 5
   5  DO 55 J = 1, N
  55  Y(J)=SAVEY(J)+(PHI(J)+F(J))*H/6.0
      M=0
      RUNGE=0
      RETURN
      END

改寫完成的 ABC FORTH 完整程式及十群範例輸入數據如下:

\ Runge-Kutta Method for ODE

INTEGER M     INTEGER J     INTEGER RUNGE
INTEGER N
10 ARRAY Y    10 ARRAY F
REAL X        REAL H
50 ARRAY PHI  50 ARRAY SAVEY

: F(RK4)     BASIC
10 REM ***F(RUNGE) = FUNCTION RUNGE( N, Y, F, X, H )
20 LET M = M + 1
30 IF M = 1 THEN 60
40 GOTO 80
50 REM ***PASS 1
60 LET RUNGE = 1
70 GOTO 9999
80 IF M = 2 THEN 110
90 GOTO 180
100 REM ***PASS 2
110 FOR J = 1 TO N
120 LET { SAVEY ( J ) = Y ( J ) }
125 LET { PHI ( J ) = F ( J ) }
130 LET { Y ( J ) = SAVEY ( J ) + 0.5 * H * F ( J ) }
140 NEXT J
150 LET { X = X + 0.5 * H }
160 LET RUNGE = 1
170 GOTO 9999
180 IF M = 3 THEN 210
190 GOTO 270
200 REM ***PASS 3
210 FOR J = 1 TO N
220 LET { PHI ( J ) = PHI ( J ) + 2.0 * F ( J ) }
230 LET { Y ( J ) = SAVEY ( J ) + 0.5 * H * F ( J ) }
240 NEXT J
250 LET RUNGE = 1
260 GOTO 9999
270 IF M = 4 THEN 300
280 GOTO 370
290 REM ***PASS 4
300 FOR J = 1 TO N
310 LET { PHI ( J ) = PHI ( J ) + 2.0 * F ( J ) }
320 LET { Y ( J ) = SAVEY ( J ) + H * F ( J ) }
330 NEXT J
340 LET { X = X + 0.5 * H }
350 LET RUNGE = 1
360 GOTO 9999
370 IF M = 5 THEN 400
380 GOTO 430
390 REM ***PASS 5
400 FOR J = 1 TO N
410 LET { Y ( J ) = SAVEY ( J ) + ( PHI ( J ) + F ( J ) ) * H / 6.0 }
420 NEXT J
430 LET M = 0
440 LET RUNGE = 0
9999 END ;

INTEGER K       INTEGER ICOUNT常微方程式之 Runge-Kutta 解法
INTEGER IFREQ   INTEGER IFPLOT
REAL RT1   REAL RT2      REAL RT3
REAL R     REAL HL       REAL C         REAL VZERO
REAL RO2L  REAL ALPHA   REAL ALPHSQ
REAL TRUY  REAL TMAX

: GROUP1
  {{ R = 100.00  }} {{ HL    =  0.50 }}
  {{ C = 2.0E-6  }} {{ VZERO = 10.00 }}
  {{ H = 0.00001 }} {{ TMAX  = 0.025 }}
  [[ IFREQ =  25 ]] [[ IFPLOT =   0  ]]
;
: GROUP2
  {{ R =    0.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00010 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =  10 ]] [[ IFPLOT =    0 ]]
;
: GROUP3
  {{ R = 1000.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00010 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =  10 ]] [[ IFPLOT =    0 ]]
;
: GROUP4
  {{ R = 1500.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00010 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =  10 ]] [[ IFPLOT =    0 ]]
;
: GROUP5
  {{ R =  100.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00001 }} {{ TMAX  = 0.020 }}
  [[ IFREQ = 100 ]] [[ IFPLOT =    0 ]]
;
: GROUP6
  {{ R =  100.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00010 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =  10 ]] [[ IFPLOT =    0 ]]
;
: GROUP7
  {{ R =  100.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00100 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =   1 ]] [[ IFPLOT =    0 ]]
;
: GROUP8
  {{ R =  100.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00200 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =   1 ]] [[ IFPLOT  =   0 ]]
;
: GROUP9
  {{ R =  100.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.00500 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =   1 ]] [[ IFPLOT =    0 ]]
;
: GROUP10
  {{ R =  100.00 }} {{ HL    =  0.50 }}
  {{ C =  2.0E-6 }} {{ VZERO =  10.0 }}
  {{ H = 0.01000 }} {{ TMAX  = 0.020 }}
  [[ IFREQ =   1 ]] [[ IFPLOT =    0 ]]
;

: PRINT-INPUT-DATA   BASIC
10 REM ***PRINT DATA
20 PRINT "      R = " ; { R }
30 PRINT "     HL = " ; { HL }
40 PRINT "      C = " ; { C }
50 PRINT " VZERO = " ; { VZERO }
60 PRINT "      H = " ; { H }
70 PRINT "  TMAX = " ; { TMAX }
80 PRINT "  IFREQ = " , IFREQ
90 PRINT " IFPLOT = " , IFPLOT
100 END ;

: INIT    BASIC
10 REM ***INITIALIZE X, Y(1), Y(2), AND ICOUNT
20 LET { X = 0 }
30 LET { Y ( 1 ) = VZERO }
40 LET { Y ( 2 ) = 0.0 }
50 LET ICOUNT = 0
60 LET N = 2 
70 END ;

: WRITE(6,201)
\ PRINT HEADINGS
  CR 10 SPACES ." X "      10 SPACES ." CALC. Y "
     10 SPACES ." TRUE Y " 10 SPACES ." CALC. Y' " CR
;

: COMPUTE-CONSTANT   BASIC
10 REM ***COMPUTE ALPHSQ AND ALPHA, PRINT HEADINGS
20 LET { ALPHSQ = 1. / ( C * HL ) - R * R / ( 4.0 * HL * HL ) }
30 LET { ALPHA = SQRT ( ABS ( ALPHSQ ) ) }
40 LET { RO2L = R / ( 2. * HL ) }
50 IF IFPLOT <> 1 THEN 80
60 REM ***CALL PLOT1
70 REM ***CALL PLOT2
80 RUN WRITE(6,201)
90 END ;

: WRITE(6,202)   BASIC
10 REM ***PRINT SOLUTION
20 PRINT { X , Y ( 1 ) , TRUY , Y ( 2 ) }
30 END ;

: DAMPED   BASIC
10 REM ***IS CIRCUIT OVER-, CRITICALLY-, OR UNDER-DAMPED
20 REM ***COMPUTE ANALYTICAL SOLUTION, PRINT OR PLOT
30 IF { ALPHSQ < 0 } THEN 50
40 GOTO 100
50 LET { RT1 = ( 1. + RO2L / ALPHA ) * EXP ( ALPHA * X ) }
60 LET { RT2 = ( 1. - RO2L / ALPHA ) * EXP ( NEGATE ( ALPHA ) * X ) }
70 LET { RT3 = ( RT1 + RT2 ) / 2.0 }
80 LET { TRUY = VZERO * EXP ( NEGATE ( RO2L ) * X ) * RT3 }
90 GOTO 170
100 IF { ALPHSQ = 0 } THEN 120
110 GOTO 140
120 LET { TRUY = VZERO * EXP ( NEGATE ( RO2L ) * X ) * ( 1. + RO2L * X ) }
130 GOTO 170
140 LET { RT1 = COS ( ALPHA * X - ATAN ( RO2L / ALPHA ) ) }
150 LET { RT2 = ALPHA * SQRT ( C * HL ) }
160 LET { TRUY = VZERO * EXP ( NEGATE ( RO2L ) * X ) * RT1 / RT2 }
170 IF IFPLOT <> 1 THEN 190
180 REM ***CALL PLOT3
190 RUN WRITE(6,202)
200 END ;

: CALL-FUNCTION-F(RK4)
  F(RK4)
;

: MAIN   BASIC	\ 執行Runge-Kutta的主要指令
10 RUN GROUP2  \ (1)輸入數據建立了十組,於此處自行選擇更換
20 RUN PRINT-INPUT-DATA
30 RUN INIT
40 RUN COMPUTE-CONSTANT
50 RUN DAMPED
60 REM ***CALL ON THE FOURTH-ORDER RUNGE-KUTTA FUNCTION
70 LET M = 0
80 RUN CALL-FUNCTION-F(RK4)
90 LET K = RUNGE
100 REM ***WHENEVER K=1, COMPUTE DERIVATIVE VALUES
110 IF RUNGE <> 1 THEN 150
120 LET { F ( 1 ) = Y ( 2 ) }
130 LET { F ( 2 ) = NEGATE ( R ) * Y ( 2 ) / HL - Y ( 1 ) / ( HL * C ) }
140 GOTO -80
150 REM *** IF X EXCEEDS TMAX, TEERMINATE INTEGRATION
160 IF { X <= TMAX } THEN 210
170 GOTO 260
\ 180 IF IFPLOT <> 1 THEN -10
\ 190 REM ***WRITE(6,203),CALL PLOT4,WRITE(6,204)
\ 200 GOTO -10
210 LET ICOUNT = ICOUNT + 1
220 REM ***PRINT RESULTS OR CALL DIRECTLY ON RUNGE
230 IF ICOUNT <> IFREQ THEN -70
240 LET ICOUNT = 0
250 GOTO -50
260 PRINT " ***Run next GROUPn by change ( 10 RUN GROUPn )*** "
270 END ;

直接操作ABC FORTH系統,執行MAIN指令,所得結果顯示如下:

MAIN
     R =     0.000000000E-1
    HL =      .5000000000
     C =     2.000000000E-6
 VZERO =    10.00000000
     H =      .0001000000
  TMAX =      .0200000000
 IFREQ =    10
IFPLOT =     0
          X              CALC. Y         TRUE Y              CALC. Y'

   0.000000000E-1      10.00000000     10.00000000          0.000000000E-1
    .0010000000         5.403029671     5.403023059     -8414.704778
    .0020000000        -4.161452687    -4.161468365     -9092.979918
    .0030000000        -9.899919391    -9.899924966     -1411.224448
    .0040000000        -6.536459532    -6.536436209      7568.001143
    .0050000000         2.836581058     2.836621855      9589.251198
    .0060000000         9.601684950     9.601702867      2794.201656
    .0070000000         7.539057071     7.539022543     -6569.818977
    .0080000000        -1.454933809    -1.455000338     -9893.586642
    .0090000000        -9.111266133    -9.111302619     -4121.250371
    .0100000000        -8.390754644    -8.390715291      5440.137662
    .0110000000          .0441656076     .0442569799     9999.894840
    .0120000000         8.438479098     8.438539587      5365.808798
    .0130000000         9.074504988     9.074467815     -4201.568624
    .0140000000         1.367486013     1.367372182     -9906.048042
    .0150000000        -7.596790229    -7.596879129     -6502.966258
    .0160000000        -9.576622425    -9.576594803      2878.902739
    .0170000000        -2.751765848    -2.751633381      9613.924740
    .0180000000         6.603046592     6.603167082      7509.961785
    .0190000000         9.887056797     9.887046182     -1498.614135
***Run next GROUPn by change ( 10 RUN GROUPn )***  ok

執行後所得結果,與原始資料內容比較,毫不遜色。


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 被編譯時是以被執行的方式編寫機器碼入系統,而非以編譯狀態來編譯,這就是最後一列程式的說明。