一個源自 ColorForth 的串聯式語言

儲存庫:https://github.com/phreda4/r3

R3forth 是一款小型、快速、串聯式程式語言,靈感來自 ColorForth。它能將程式編譯成原生 64 位元程式碼,專為直接與作業系統互動、即時應用程式及遊戲而設計。

如果您曾使用過 Forth,會很快感到熟悉。如果您沒有 — 別擔心。R3forth 擁有一個非常小的核心,易於學習,本教學將引導您一步步完成。

給 Forth 程式設計師的注意事項:與大多數 Forth 實作不同,R3 會先編譯整個程式,然後再執行。本手冊中「推入堆疊」等詞句描述的是執行階段的行為,而非互動式 REPL 的行為。

電腦需要程式才能運作 — 精確描述它應該做什麼。

這個描述是用機器能理解的語言寫成的。但程式設計的過程更早開始。

首先是個想法 — 例如,畫一個圓。然後我們寫程式碼讓機器畫一個半徑為 1 的圓:

電腦首先將此程式碼翻譯成機器碼,這是它唯一真正理解的語言。這個過程稱為編譯。

編譯完成後,結果就會被執行。如果程式碼無法編譯,就會出現錯誤:

當我們嘗試編譯時,電腦會回報錯誤且不執行任何操作。另一種可能性是程式碼編譯正確但並未達到我們的預期 — 這稱為邏輯錯誤。

要建構一個可運作的程式,我們需要:

重要提示:我們只能程式化我們足夠理解並能描述的事物。我們無法程式化我們不知道如何運作的事物。

程式設計意味著建構一個描述行為的食譜。這個食譜稱為原始碼或程式。

程式有兩種定義:

作為資料,我們需要儲存代表以下內容的數字:

作為程式碼,我們建構動作。任何行為都可以用四個元素來表達:

原始碼是一個文字檔案,分割成「單字」。單字是任何由空格分隔的字母、數字或字元序列:

大小寫不敏感 — LIVES 和 lives 是同一個單字。

所有操作都透過資料堆疊使用後綴表示法進行:運算元在前,運算子在後。

重要提示:空格定義了單字的邊界。一個非常常見的錯誤來源是遺漏空格。空白字元的確切形式並不重要 — 一個空格、多個空格或換行符都等同。

R3 識別 8 種前綴。單字的第一個字元決定了它的解釋方式:

注意:位址前綴 ' 需要一個有效的使用者定義單字。基礎字典單字沒有可定址的位置。

語言以一個預定義的字典開始,包含約 200 個單字,代表基本的電腦操作(請參閱參考文件)。

使用 : 和 # 前綴來新增單字。當編譯器搜尋單字時,它從最後定義的往前搜尋。您可以重新定義單字 — 只有最新的定義會被使用。

程式設計就是創造單字。一個程式是一個不斷成長的字典,直到您呼叫最後一個單字,它就會執行整個程式。

^ 前綴包含外部檔案並匯入其匯出的定義:

這提供了乾淨的模組系統:只有您打算共用的 API 會變得可見。

單字必須在使用前定義:

第一行定義了一個值為 5 的變數。第二行定義了 square:複製堆疊頂端的元素並將兩個副本相乘。第三行是程式的進入點 — 它推入 5(side 的值),然後呼叫 square。

分號 ; 結束單字的執行並返回呼叫者。這意味著:

沒有結尾 ; 的單字定義會「掉落」到下一個定義。這是一個刻意設計的功能:

這避免了當一個單字總是導向另一個單字時額外的呼叫開銷。請有意識地使用它,並用類似 | fall through to word2 的註解來記錄。

資料堆疊是 R3forth 的主要工作區。將其想像成一疊盤子:您只能從頂部添加或移除。您最後放入的值是第一個被取出的 — 這稱為 LIFO(後進先出)。

所有操作都從堆疊讀取輸入並將結果寫回堆疊。

當數字出現在原始碼中時,它們會按順序推入堆疊。

每個單字可以接受和/或在資料堆疊上留下值。註解描述了單字之前和之後的堆疊狀態,以 -- 分隔:

逐步範例 — 追蹤每次操作後的堆疊:

有些單字產生值(如 DUP),有些消耗值(如 DROP),有些則兩者都做。

堆疊平衡:如果您的單字導致堆疊無限增長或意外清空,程式就會出錯。這通常無法在執行前偵測到。

堆疊用於在單字之間傳遞值,而不是儲存資料。如果您需要真正的堆疊結構,請自行建構:

*/ 在比例縮放方面特別有用:

R3forth 使用 48.16 定點數格式:48 位元用於整數部分,16 位元用於小數部分,所有這些都儲存在標準的 64 位元儲存格中(與整數和位址使用的儲存格相同)。

實際規則:將任何實數乘以 65536 (2¹⁶) 以獲得其內部表示。

由於只有 16 位元用於小數,因此整數範圍非常龐大(高達約 281 兆),實際上幾乎不可能發生溢位。

語言識別帶有小數點的字面值並自動轉換它們:

加法和減法與整數完全相同。乘法和除法需要來自 r3/lib/math.r3 的特殊單字:

角度以「圈」表示(而非度或弧度):1.0 = 完整圓,0.5 = 180°,0.25 = 90°。

對於其他位元分配,R3 提供了:

括號標記程式碼區塊。它們也是單字,必須用空格分隔:

條件單字後面跟著一個程式碼區塊,僅在條件為真時執行該區塊。

有兩種類型的條件判斷,具有不同的堆疊行為 — 理解這種區別至關重要。

一元條件判斷在不消耗堆疊頂端值的情況下檢查它。該值保留在堆疊上,可供下一次比較或 drop 使用。

二元條件判斷比較 NOS(a)與 TOS(b)。它們會消耗 TOS 並在堆疊上留下 NOS,無論結果如何。將 a b >? 視為詢問「a 是否大於 b?」— 正如您在數學中會寫的那樣。

由於二元條件判斷會在堆疊上留下 a (NOS),您可以對同一個值進行多次鏈式測試:

實際規則:a b >? 詢問「a 是否大於 b?」留在堆疊上的值始終是 a (NOS)。

任何測試後,原始值仍在堆疊上。您必須明確移除它:

鏈式操作之所以有效,是因為值在測試之間持續存在:

R3forth 刻意省略了 IF-ELSE。這不是限制 — 這是一個設計選擇,鼓勵您將邏輯分解為小型、命名的單字(這種做法稱為「因子化」)。結果是程式碼讀起來幾乎像英文,並且更容易測試和重用。

要在絕對必要時複製 IF-ELSE,請使用提前退出:

對於連續整數,請使用跳轉表:

對於非連續值,請鏈式比較並提前退出:

錯誤 1:忘記清理堆疊

錯誤 2:誤讀哪個值被消耗

錯誤 3:巢狀條件判斷未清理

當條件判斷放在程式碼區塊內時,它就變成了一個迴圈。只要條件為真,區塊就會重複執行。當條件為假時,執行會跳轉到 ) 後面的單字。

當 TOS 達到 10 時,<? 為假,迴圈結束。計數器在之後被丟棄。

一元條件判斷 ( 1? ) 不消耗堆疊,使倒數迴圈更快:

是否包含 0 取決於您放置程式碼的位置:

帶有累加器的迴圈 — 三種風格:

遞迴自然發生:一旦單字定義開始,該單字就可以呼叫自身。

遞迴規則:始終確保終止條件正確,並且堆疊在基本情況和遞迴情況之間保持平衡。

當一個單字在 ; 之前被呼叫為最後一個操作時,R3 會將呼叫轉換為跳轉而非實際呼叫 — 不會發生堆疊增長。尾遞迴單字會變成迴圈:

當問題自然分解為更小的相同子問題時(樹狀結構、分治法),請使用遞迴。對於線性遍歷和簡單計數,請使用迴圈。

變數定義了記憶體中的命名位置。實際位址在編譯時分配 — 您無需知道它,只需使用名稱即可。

以 *1000 作為範例基底位址:

# 定義內的 * 語法不是乘法運算子。它保留了指定位元組數的零初始化的區塊:

變數名稱成為區塊開頭的指標。與列出值比較:

當您需要一個在執行時填寫的緩衝區或陣列時,請使用 *。

記憶體佈局:每個預設儲存格為 8 位元組(64 位元)。連續儲存格之間相隔 8 位元組。

語言透過 MEM 暴露了可用記憶體的起始位置。在此之外,三個單字管理一個簡單的基於堆疊的配置器:

此方案的優點:無需垃圾收集器。

文字是記憶體中的位元組序列。原始碼中的字串用雙引號括起來:

當 R3 遇到字串字面值時,它會在記憶體中儲存位元組,附加一個零位元組(null 終止符),然後將第一個字元的位址推入堆疊。

字串儲存在字串常數區域,每次單字執行時都會推入位址。

位元組儲存在變數記憶體中,包括 null 終止符。'greeting 是位址。

.print 單字處理格式字串,其中包含 % 佔位符,並從堆疊中消耗值:

注意:格式字串按照 % 佔位符的順序,從右到左消耗堆疊上的值。如果您需要在列印後將值保留在堆疊上,請使用 dup 或 over。

若要建立字串指標陣列:

若要建立連續的字串區塊:

暫存器 A 和 B 是兩個快速的輔助變數,針對記憶體遍歷進行了優化。與堆疊值不同,它們在單字呼叫之間保持不變。

主要用途:將位址載入 A 或 B,然後使用 A@+ / A!+ 依序讀取或寫入。這比在資料堆疊上管理指標算術更乾淨。

暫存器 B 具有相同的操作:>B , B> , B+ , B@ , B! , B@+ , B!+ 等。

search 使用暫存器 A 中的位址。第一個迴圈使用 list1,第二個使用 list2 — 無需將位址通過堆疊傳遞。

暫存器在單字呼叫之間保持不變。當您必須呼叫另一個可能使用暫存器的單字時,請先儲存它們:

✓ 好處:線性記憶體遍歷、迴圈中的累加器、單一單字內臨時位址儲存。

✗ 避免:跨多個單字的長期儲存(請改用變數),或任何會使程式碼難以理解且無文件記錄的地方。

還有第二個堆疊處理單字呼叫 — 每次執行 ; 時儲存返回位址。

謹慎使用:只要每次 >R 都與 ; 之前的 R> 相匹配(包括條件退出),就可以安全地將返回堆疊用作單一單字內的臨時儲存。避免跨單字邊界使用它 — 每個單字都必須將返回堆疊恢復到其原始狀態。

R3forth 透過動態連結庫(Windows 上的 .dll,Linux 上的 .so)連接到作業系統。這允許您從 R3 程式碼呼叫任何 C 相容的函數。

最簡單的起點 — 只需包含主控台函式庫:

平台支援:R3 專為 Windows、Linux、macOS(未來)和 Raspberry Pi(未來)設計,儘管這些移植工作仍在進行中。

在 .input 之後,輸入的文字會儲存在 ##pad 變數中。

注意:偵錯工具仍在開發中。

堆疊操作速度最快。將常用值保留在堆疊上,而不是變數中。

倒數迴圈更快,因為 1? 不會消耗計數器:

一旦知道答案就立即退出:

為了清晰起見進行因子化,但在緊湊的迴圈中內嵌瑣碎的操作:

R3forth 教學 — 請參閱配套的參考文件以取得完整的基礎字典。