十月 30, 2011
» Extending Gforth with the Code Word

很久沒發文了,但一直都有記錄筆記的習慣,原本也是打算整理好發表的,但常常寫到一半就去做其他事了,所以積了不少未完成的稿XD 這次出清一篇筆記,來談談 forth 中的 code word。

一般對於 forth 系統的擴展有兩種方式,最方便的當然是定義新的 colon word,另一種則是實作比較低階的 code word。

code word 代表的是低階實作,也就是直接跟底層平台對話。以 lisp-based forth 為例,其實就是把 lisp vm 當作(虛擬)硬體平台,因此 code word 作用大概就是產生一堆 lisp form,而 x86 native forth 當然就是產生 x86 machine code 了。

簡單地說,code word 需要負責兩件事:

  • 產生 native code
  • 讓 forth 可以順利執行這些 native code

產生 native code 的工作通常會由一個 target assembler 負責。這也是為什麼一個完整的 forth system 多半會提供自己的 assembler。雖然 forth 已經是可擴展的設計,但在需要直接操作宿主平台時,可能會需要寫成 code word 比較方便。

比較麻煩的是第二點,讓 forth 可以順利執行的關鍵在於不能干擾系統運作。forth 本身雖然是 stack vm,但還是有幾個基本的暫存器,一般在 x86 上會對應到實際的暫存器。依照實作模型的不同,可能會需要佔用 2 至 5 個不等。在 code word 中必須妥善管理這幾個暫存器,不是單純 pushapopa 就可以解決的。最常見的狀況,就是維護 SP 的值 (forth 中的 SP 暫存器,非 x86 的),確保參數可以正常傳遞,不會發生 underflow;以分支指令來說可能需要修改 IP 的值,只要改錯暫存器就會發生災難。因此第一步就是找出這幾個暫存器的對應關係。

在一般的 forth 系統中,通常會提供一個叫做 see 的 disassembler,可以透過它來猜出系統的規劃。Gforth 套件提供了好幾種實作,由於接下來的實驗在不同實作中會有不同結果,為了方便解說只使用 gforth-fast。在 Gforth 中,第一個建議觀察的是什麼事都不會做的 noop

see noop
Code noop
( $804B1BF )  add     esi , # 4  \ $83 $C6 $4
( $804B1C2 )  mov     ebx , dword ptr FC [esi]  \ $8B $5E $FC
( $804B1C5 )  mov     eax , ebx  \ $89 $D8
( $804B1C7 )  jmp     804B03C  \ $E9 $70 $FE $FF $FF
end-code

從這邊可以猜到不做事的時候,系統大概會使用 eaxebxesi,然後跳到某個位址繼續執行。因此觀察其他 word 的時候就可以先去除這幾個動作,找出真正關鍵的操作。接下來建議的是 dupdrop。這兩個 word 是基本的堆疊操作,可以直接找出 SP 在哪,甚至可以看出是否有加入 TOS (Top Of Stack) 的快取設計,也就是將 TOS 放在特定的暫存器以減少一次記憶體操作。

see drop
Code drop
( $804C836 )  add     edi , # 4  \ $83 $C7 $4
( $804C839 )  add     esi , # 4  \ $83 $C6 $4
( $804C83C )  mov     ebp , dword ptr [edi]  \ $8B $2F
( $804C83E )  mov     ebx , dword ptr FC [esi]  \ $8B $5E $FC
( $804C841 )  mov     eax , ebx  \ $89 $D8
( $804C843 )  jmp     804B03C  \ $E9 $F4 $E7 $FF $FF
end-code

drop 的內容可以看到除了 noop 以外,還操作了 edi,沒有意外的話 SP 就是它了。同時也知道堆疊往低位址成長 (因為 drop 相當於做了一次 stack pop)。作為佐證可以再觀察一下 dup,應該會看到減少 edi 內容值的運算:

see dup
Code dup
( $804C85D )  sub     edi , # 4  \ $83 $EF $4
( $804C860 )  add     esi , # 4  \ $83 $C6 $4
( $804C863 )  mov     dword ptr 4 [edi] , ebp  \ $89 $6F $4
( $804C866 )  mov     ebx , dword ptr FC [esi]  \ $8B $5E $FC
( $804C869 )  mov     eax , ebx  \ $89 $D8
( $804C86B )  jmp     804B03C  \ $E9 $CC $E7 $FF $FF
end-code

看起來的確有移動 edi 內容的操作。另外還看到奇怪的動作,把 edp 的內容搬進 stack top – 1 的位置,並不是移動到 stack top,推測 可能有 TOS 的設計。

要證實這一點,可以再看一下 swap+ 這幾個會影響 TOS 的 word。

see +
Code +
( $804B918 )  add     ebp , dword ptr 4 [edi]  \ $3 $6F $4
( $804B91B )  add     esi , # 4  \ $83 $C6 $4
( $804B91E )  add     edi , # 4  \ $83 $C7 $4
( $804B921 )  mov     ebx , dword ptr FC [esi]  \ $8B $5E $FC
( $804B924 )  mov     eax , ebx  \ $89 $D8
( $804B926 )  jmp     804B03C  \ $E9 $11 $F7 $FF $FF
end-code

看來的確是把運算後的結果 (TOS) 直接放到 ebp 了,而不是放在記憶體中。拿到 SPTOS 後就可以試著增加一個 code word 看看了。首先用最簡單的 dup 操作來驗證前面的猜測,實作一個有相同操作的 _dup

code _dup
    bp di ) mov
    4 # di sub
    next
end-code

3 _dup .s

可以看到堆疊中的確出現兩個 3 了。不過看起來自己實作的 _dup 好像比原版的 dup 還要精簡一點。另外也可以寫個 _+ 看看:

code _+
    4 # di add
    di ) bp add
    next
end-code

2 3 _+ .s

結果也正確無誤,留下一個 5。到這邊已經可以做很多事了。不過偶爾可能會需要 RP,所以從 >r 找到 RP 放在記憶體的某處。查找的過程不再列出,同樣複製一個相同的 _>r 來驗證看看:

code _>r
    4 # $3c sp d) sub
    3c sp d) bx mov
    bp bx ) mov

    4 # di add
    di ) bp mov
    next
end-code

為了維護 return stack, >rr> 通常要平衡使用才不會破壞系統運作,所以我將自己實作的 _>r 搭配系統的 r> 一起使用,測試過可以正常執行。

除了這幾個重要的暫存器外,還有一個 IP,可以透過觀察 branch 找到,這裡就不再贅述了。不過還有一件重要的事需要特別提出來討論。在整理這篇筆記的過程中,我發現一個嚴重的問題:以前實驗的結果,在 0.7.0 版的 gforth-fast 可以正常運作,但換成 0.7.9 版卻無法動作,後來試了一下 0.6.2 也同用無法執行。查了文件才知道原因在於 c compiler 版本。由於不同版本的編譯器執行 register allocation 結果可能不同,會產生出完全不一樣的 forth vm,這點是特別要注意的。前面列出來的結果是在 gcc 4.4.4 建立出來的 gforth-fast 0.7.9 的環境重新實驗的。如果使用到的暫存器剛好都是自由的 (不被系統使用),換系統時就不需要再調整程式了。

雖然聽起來有點糟,但對我來說也不算什麼大問題就是了。畢竟 forth 在各系統間的相容性本來就不能期待,會想使用 code word 的人大概對正在使用的系統也有一定的掌握能力了,這樣程度的相容性問題應該都有能力處理。


會翻出這篇筆記,主要是看到有人做了一個 benchmark,gforth-fast 需要的時間是其他 forth 的好幾倍,想試試把幾個關鍵處改寫成 code word,看有沒有機會加速。最後實驗的結果是… 不行!

囧rz

非但沒變快,反而更慢了XD 也許 Gforth 在切換成 code word 可執行環境時做了很多事,因此效能不如全部都使用 colon word;也許是 instruction miss 等原因造成的吧。這個一時興起的實驗提醒了我在選系統時要記得同時測試一下 code word 的使用,暫時還是先把 Gforth 的 assembler 當作 cross assembler 使用吧。


Filed under: forth, Note

十月 26, 2011
» TestPost

不好意思,之前發現 emacs 沒辦法發出文章了,這篇文章單純測試用 測完沒問題就刪掉 <!–more–> 測試程式碼 <pre class="src"> <span style="color: #00cdcd;">init</span>: <span style="color: #00cd00;">xor</span> ax, ax <span style="color: #00cd00;">mov</span> ds, ax <span style="color: #00cd00;">mov</span> es, ax </pre> <p>測試<a target="_blank" href="http://www.google.com/">超連結</a>是否正常</p> <ol> <li>item 1</li> <li>item 2 <ul> <li>qwer <ul> <li>abcde</li> </ul></li> <li>tyui</li> </ul></li> <li>item 3 <ul> <li>mjkl</li> <li>eoru</li> </ul></li> <li>item 4</li> </ol>

四月 21, 2011
» VOCABULARY in Forth

最近找了一些 Forth 的舊文章來看,也看了一些舊程式碼。在這些資料中發現很多以前沒學會的東西,正好趁這機會補習一下。這次要介紹的是 Forth 裡的 VOCABULARY

如果對 Forth 有一點瞭解,或者看過我以前翻譯的 PForth 教學,應該會知道 Forth 中的每一個執行單位叫做 word。相對於其他程式語言來說,就像是函式那樣的東西。Vocabulary 的意思是「詞彙」,簡單的說就是 word 的集合。更進一步,大概就像是 Library 的設計。

以上是我一直以來對 Vocabulary 的理解。以前我還在念書時,想找個程式語言來學,卻糊裡糊塗入了 Forth 坑。但當時 Forth 並不是主流,不但買不到書,也找不到人教,很多東西就這樣斷章取義,隨意理解了。等到實際要用時才知道問題大條,趕緊花了點時間玩一下,原來誤會了那麼久的東西,其實是那麼簡單又強大的設計。

其實 Vocabulary 除了 Library 的功能之外,更重要的功能其實是 Namespace 的控制。

Forth 的核心設計有幾個重要的基礎元件,其中的 Dictionary 用來存放資料與新定義的 word。Vocabluary 可以將 Dictionary 中的 word 分組,並影響後續使用的搜尋順序。用比較現代一點的說法,其實就是 Namespace 與 Scope 機制。以下會簡單介紹一下 Forth 的 Vocabulary 設計與運作方式,參考資料來源是 GForth 附的程式碼與實驗結果。

在 Forth 系統誕生之時會提供一個最基礎的 Namespace,在這個 Namespace 裡就只有最基本的 Namespace 定義與管理工具,連基本的堆疊運算辦不到。以 GForth 為例,這個最基本的 Namespace 叫做 Root,只提供了 5 個 word:

order set-order forth-wordlist Forth words

其中比較重要的是 ORDERFORTH 這兩個 word。 ORDER 的作用是列出目前作用中的 Namespace,以及目前的定義空間; FORTH 是一個 Vocabulary,裡面定義了撰寫 Forth 程式會使用到的 word。先來看看 ORDER 的結果,在 GForth 中輸入 ORDER 會印出:

Forth Forth Root     Forth

這串訊息要分成三部份來看:

  1. 最左邊的 Forth:代表目前作用中,搜尋順序最高的 Vocabulary。依照以前的術語,這個位置代表 CONTEXT Vocabulary
  2. 最右邊的 Forth:代表目前的定義空間,所有新定義的 word 將會加入 Forth 這個 Vocabulary 中。依照以前的術語,叫做 CURRENT Vocabulary
  3. 中間的部份:一個 Vocabulary 清單,代表目前的搜尋順序。當系統在 Context Vocabulary 中找不到要執行的 word 時,就會依照這邊的順序一個一個找下去

以這個顯示結果來說,在 C++ 看到的可能是:

using namespace Root;
namespace Forth
{
  ...
}

這三個部份都是可以隨意改變的。在 Forth 中,每個 Vocabulary 其實也是一個可執行的 word,執行後的動作就是更新 Context Vocabulary。Forth 允許兩個同名的 word 定義在同一個 Vocabulary 或不同 Vocabulary。定義在同一個 Vocabulary 時,只會搜尋到最後定義的 word;而定義在不同 Vocabulary 時,就是靠 Context 決定會找到哪一個。

舉個簡單的例子來說明。假設我有 A 和 B 兩個 Vocabulary,其中 A 定義了兩個 foo:

: foo ( -- )
  ." in a" cr
  ;

: foo ( -- )
  ." in a (2)" cr
  ;

B 也定義了一個同名的 foo

: foo ( -- )
  ." in b" cr
  ;

假設我想執行 A 裡面的 foo,必須先切換 Context 到 A:

\ Set Context
A  ok

\ Exec foo
foo in a (2)
 ok

可以看到真正執行的是最後定義的 foo。若想執行 B 裡面的 foo,同樣也要先切換 B:

B  ok
foo in b
 ok

除了切換 Context 外,其他兩個部份也可以隨意設定。但在介紹方法之前得先引入幾個小工具。Forth 對於整個 Vocabulary 的管理提供了幾個小工具。比較重要的有 VOCABULARYONLYALSODEFINITIONS

VOCABULARY 可以建立新的 Vocabulary。以剛剛的例子來說,建立 A 與 B 的方法是:

vocabulary A  ok
vocabulary B  ok

ONLY 的意思是只搜尋 Root 這個 Vocabulary。在 GForth 中可以看到 ORDER 的結果是:

only  ok
order Root Root     Forth  ok

可以看到去除左右兩個 Vocabulary 後,中間的搜尋清單只剩下 Root。

ALSO 的意思是說,將目前的 Context Vocabulary 加入搜尋清單。加入以後,不管 Context 怎麼變動,都可以不受影響。以前面的例子繼續實驗。我把 A 加入搜尋清單後,再改變 Context,看看是否可以找到定義在 A 的 foo:

only forth also \ reset search list
A also          \ add A to search list
forth           \ set Context

\ current environment
order Forth a Forth Root     Forth

\ exec foo
foo in a (2)
 ok

看來的確可以自由配置搜尋清單。

最後一個是 DEFINITIONS,用來決定新定義的 word 該屬於哪一個 Vocabulary,執行後會把 Context Vocabulary 設定到 Current Vocabulary。以前面的例子來說,我想在 A 裡面定義一個 foo,可以這樣做

A definitions
: foo ( -- )
  ." in a" cr
  ;

Forth 的 namespace 設計很簡單,卻威力強大。只加了這幾個工具就可以產生一些好玩的應用。舉例來說,為了 debug 方便,我會在 word 定義中加上一些 log 訊息。有時候跑一次程式後再檢查 log 會比進入 debugger 有效率得多。例如:

: foo ( -- )
  \ ." >> enter foo" cr
  ." exec foo" cr
  \ ." << exit foo" cr
  ;

比較麻煩的是加了 log 就得移除,需要一直修改程式。利用 Forth 的特性,我可以在不同的 Vocabulary 設計一組外掛用的 wrapper word,專門用來顯示一些 log 訊息:

vocabulary tool
tool also definitions
: foo ( -- )
  ." exec foo" cr
  ;

vocabulary tool-debug
tool-debug definitions
: foo ( -- )
  ." >> enter foo" cr
  foo
  ." << exit foo" cr
  ;

可以看到我在 debug 用的也是同名的 foo,但因為定義在不同 Vocabulary,所以不會互相干擾。在一般情況下我可以正常地執行 foo:

only forth also definitions tool  ok
foo exec foo
 ok

需要追蹤執行流程時,就將新工具外掛進來

tool-debug  ok
foo >> enter foo
exec foo
<< exit foo
 ok

不需要 log 的話可隨時換回正常版的 foo。

雖然很容易就做到這樣有趣的效果,但其實這只是簡單的示範而已, 並不具太大的實用價值。要達到比較實用的程度,可能得搭配 Forth 的 DEFER word 機制,將 log 輸出功能設計成一個可抽換的 policy word,並在執行前決定要設置成哪一個 policy1。運作起來類似 late binding 的效果。

Forth 這麼古老2的東西 (其實不比 Lisp / Fortran 晚多少喔),竟然連 Namespace 管理機制都設計進去了,以現代程式語言的角度來看還真是一點都不退流行阿!而且 Forth 流行的時代,其實主要做一些硬體控制的工作,有複雜到需要加入 Namespace 機制嗎3?在那個高階語言都還不太流行的年代,天曉得這些前輩高人們怎麼想到的?


1. 應該很多人猜得到這是從哪本書借來的點子

2. 想瞭解 Forth 的誕生,可以參考 Forth – The Early Years (中譯) 這篇文章

3. 事實上就是有這個需要,希望以後有機會能介紹到這部份


Filed under: forth

十月 31, 2009
» Test Yourself

超過半年沒新文章了XD

每次看著空虛的 blog 就想把一些庫存丟出來充充版面,但有些內容過了該發表的時間點就沒什麼動力寫完,只好繼續讓 blog 就這樣晾著。沒寫文章的這陣子,程式也沒多寫,大概都把時間耗在 plurk 和 irc 上了 (也撥了一些時間看看土曜劇和晨間劇啦)。倒是最近遇到一個想用 forth 寫輸入法的狂熱份子,又喚醒我心中的 forth 魂了。

forth 毫無疑問是我最喜愛的程式語言,但本 blog 的 forth 分類卻只有屈指可數的兩篇文章。實在是因為我除了把 forth 當作頭腦體操外,根本很少使用 forth 來寫正式的程式 (而且功力也差…)。話雖如此,閒暇時仍舊會在 google 上搜尋相關資料,思考著怎麼利用在 forth 中的體會,幫助其他開發工作。但大多數時間還是抱著好玩的心態來看 forth,偶爾想到就會找出來玩一下。

這次嘗試了 reva 這套 forth engine,雖然提供了很多方便的函式庫,但卻不相容於 ANS 標準,所以一開始吃了不少苦頭。花了一點時間看了實作,再把以前在 pforth 上寫的練習程式移植到 reva 後,慢慢搞清楚 reva 與 ans 之間的差異了 (有些 word 的確方便多了)。

另外就是玩一下 ffi 的部份。第一個練習就是來個 vte,根據 caleb 提供的範例程式改寫一下 (去掉 turnkey 的部份):

" libvte.so" lib libvte

0 func: vte_terminal_new
8 func: vte_terminal_fork_command

needs ui/gtk2
~gtk2

: =exit callback gtk_main_quit ;
: main ( -- ) init
    GTK_WINDOW_TOPLEVEL gtk_window_new dup dup
    vte_terminal_new
    dup 0 0 0 0 0 0 0 vte_terminal_fork_command drop
    2dup _add
    z" child-exited" ['] =exit _connect drop
    z" delete-event" ['] =exit _connect drop
    gtk_widget_show_all gtk_main ;
main bye

直接翻譯自原本的 c 程式,除了變數都透過 stack 來操作外,看起來實在沒什麼不同。事實上也不過是把 forth 當作 libvte.so 的外殼,透過交談式界面來操作,相當方便。執行結果如圖:


有了 ffi 後,原本在 c 世界的資源都可以拿來利用了,而且寫起來也有趣多了。即使不特別用 forth 風格來撰寫,當作 c 來寫也不是太困難的事,實作邏輯很相近 (就像很多人拿 c++ 來寫 c 一樣…)。


話說回來,forth 提供的語法其實比 c 豐富很多,應該要被歸類在比 c 還高階的語言才對阿!思考到這裡,想起以前嘗試過用 forth 實作 Higher-order Function,就是企圖在 forth 中使用高階的工具。

其實剛接觸 forth 時一直都是當作比較方便的組合語言來學。看了 Joel 的文章後,覺得在其他語言也應該享受到 HOF 的方便,再加上那時也學了一陣子程式設計,正好試試看是不是比 Joel 大一時還厲害 (笑)。不過 Python 或 Perl 這幾個常用的語言早就內建類似的工具了,最後就找了 c 和 forth 來玩。c 的版本很多人玩過,我就不再多說了,還是聊聊 forth 吧。

有嘗試過用 functional 風格寫 c 的人應該會知道這兩種不同程式風格不是那麼容易結合,一方面是 lisp 的設計讓這些高階運算很容易達成;而以凡事以 list 為運算對象的概念,也跟 c 不太一樣。但在 forth 裡卻很容易跨越這層隔闔,雖然 list 物件得另外實作出來,但偷懶一點還是可以用 stack 來模擬的。只要把前置式語法改為後置式語法,也許很多在 functional 世界發展成熟的設計手法就可以直接搬到 forth 使用了。

以 Currying 來說,在 forth 裡再自然不過了。c 的實作可以參考 jservthinker 的魔法,而 forth 呢,其實在標準的 word 中就有不少是帶有 currying 風,如 0<0=1+2/ 以及 cell+cells。大概因為太自然了,即使 forth 圈沒有刻意使用 currying 來稱呼 (就我所知是沒有),幾乎是平常就在做的事。以 Wikipedia 上舉的例子來說明:

Take the function f(x,y) = y / x.
To evaluate f(2,3), first, replace x with 2.
Since the result is a new function in y, this function g(y) can be defined as
g(y) = f(2,y) = y / 2.
Next, replacing the y argument with 3,
provides the result, g(3) = f(2,3) = 3 / 2.

在 forth 中可以寫成:

: f ( y x -- n ) / ;
: g ( y -- n ) 2 f ;

3 2 f . cr
3 g . cr

透過 forth compiler 直接將參數 (也就是 2) 編譯到 forth dictionary 中,往後使用 g 時就會自動將 2 傳給 f。可以看到在 forth 裡使用 currying 有多麼自然。

那麼其他的 HOF 呢?回到 Test Yourself 這篇文章,文中第一題出現的 accumulate,可以在 SICP 的 2.2 節看到,事實上就是 fold-right。在 python 中有個很像的工具,叫做 recude (但 reduce 應該是 fold-left),摺疊的方向是不同的。要自己實作一個來用也不難,根據這裡提供的 Haskell 程式:

foldr f z []     = z
foldr f z (x:xs) = f x (foldr f z xs)

可以知道 foldr 的實作關鍵在於從 list 的右邊開始摺疊,以遞迴的想法來說就是先呼叫到底,直到遇到終止條件 (empty list) 再開始回頭計算。釐清這個想法後就可以翻譯成 forth 程式,但為了方便,先定義一些方便的小工具:

hex EFABCD constant nil
: car ( addr -- n ) @ ;
: cdr ( addr -- addr' ) 1 cells + ;
: null? ( addr -- t ) @ nil = ;

因為偷懶不想實作 list object,這邊先用陣列來模擬,在最後塞一個 nil 作為辨識用。 carcdrnull? 的定義可以參考 lisp,而且也可以看到這裡的 cdr 用到了上面提到的 currying。有了這些工具後就可以輕鬆地翻譯 accumulate 了:

: foldr ( addr init xt -- n )
    rot dup null? if 2drop        ( null list, left init on the TOS and return )
        else over >r              ( store f )
                dup car >r        ( store x )
                cdr -rot recurse  ( exec "foldr f z xs" )
            r> r> execute         ( exec "f x n" )
        then ;

: accumulate foldr ;

這裡參考了 haskell 使用的符號,在每一行後面加上註解,可以對照著 wikipedia 給的範例程式和 accumulate 的 scheme 程式看,對於初次接觸 forth 程式的人應該會有幫助。但要注意的是,這裡只是示範性質的實作,畢竟 forth 的內涵跟 c 一樣是個 stack,遞迴深度過深還是會發生 stack overflow 的。

有了 accumulate 後,原題的平方和就可以很輕鬆地 組裝 出來了:

: square ( n -- n' ) dup * ;
: addsq ( a b -- n ) square + ;
: sum-of-square ( list -- n ) 0 ['] addsq accumulate ;

這裡又可以看到 currying 啦!接著只要丟一個 list 就可以了

create list 1 , 2 , 3 , 4 , 5 , nil ,

list sum-of-square . cr

執行完應該可以看到螢幕上出現答案 55。

以上使用的應該可以在任何一個相容 ANS 標準的 forth engine 執行,我自己用的是 pForthficl。裡面用到的一些基本字用法可以參考以前翻譯的教學

建構出基本工具後就可以來寫 SICP 上的作業了。這個題目希望用 accumulate 實作出 sum 和 product,對於學習過函數式程式語言的人應該都不陌生。用 forth 寫起來會像這樣:

: sum     ( list -- n ) 0 ['] + accumulate ;
: product ( list -- n ) 1 ['] * accumulate ;

把剛剛產生的資料餵進去看看:

list sum     . cr
list product . cr

可以看到螢幕上出現 15 和 120。這裡的 product 其實就是 factorial,但一般會設計成接受一個整數參數,而不是一個 list。不過只要稍微包裝一下 product 就可以產生出 factorial 了:

: scratch ( -- addr ) pad 4 cells + ;
: seq ( n -- addr ) scratch swap 1+ 1 do
    i over ! cell+ loop nil swap ! scratch ;
: fact ( n -- n' ) seq product ;

5 fact . cr

到目前為止除了 foldr 比較長一點,其他都是短短的程式。在 forth 的世界中,一般就是這樣透過基本字來組合來建構出複雜的程式,所以也有人說 forth 就像程式語言中的樂高積木一樣。但要設計出好用、好組裝的積木,就需要經驗了。太大的積木不好用,而且複雜不容易維護,所以重新拆解成更小單元;抽出來的積木不好重複利用,則需要重新分析所有程式的演算法核心性質。仔細一想,這不就是 refactoring 的概念嘛!但對於使用 forth 的人來說,不需要別人推廣也不用特別學習,很自然地就在做這件事,好像古老的技術思維又和現代連結上了,而且一點也不會過時。

進一步思考下去,fold 的出現其實也是在對演算法進行分解後,得到的通用工具。其他還有 map、filter 和 zip 等小工具,就像前面提過的,這些成熟的演算也可以回饋到 forth 程式來使用。其實從以前就覺得 forth 有許多資料和程式實作技巧並不是很透明,也不像主流語言這樣持續發展且容易取得;一方面關注的人也不多就是。雖然對於推廣 forth 不太有興趣,不過從現在的技術中找出連結,引入可用的工具,對於 forth 應該會有全新的認識…吧,我想。


Posted in forth

二月 11, 2009
» Line wrap in Emacs

在大部份的編輯器中,對付太長的文字列通常用的是自動換行 (line wrap),就我目前所知,在 Emacs 中有以下幾種不同方式可以選擇:

Line wrap

預設行為即是一般常見的 line wrap。在這種環境下的文字內容並不會被更動,僅在顯示時將超出視窗邊界的部份顯示在下一行,同時在換行處加上一個記號 (如箭頭標示處):


在切割視窗時情況不同:


可以設定一下得到相同行為:

(setq truncate-partial-width-windows nil)


Truncation

打開 truncate line 以後,超出視窗邊界的部份不顯示,同時會在邊界處顯示一個特殊符號作為識別 (與 line wrap 時用的符號不同)。一般適合在寫程式的時候使用

M-x toggle-truncate-lines


也可以寫到 .emacs

(setq default-truncate-lines t)

為了方便可以設個快速鍵:

(global-set-key [f11] 'toggle-truncate-lines)

Filling text

fill 是破壞性操作,會根據 fill-column 的值強制在特定位置插入換行字元。手動操作時只需要按下 M-q 即可,或是乾脆啟用 auto fill:

M-x auto-fill-mode


fill-column

換行寬度可以自行修改,因為設定 fill-column 要求一個數值當作參數,:

C-u 60 M-x set-fill-column

C-u 60 C-x f

fill-prefix

fill-prefix 的內容會自動加到被修改的文字列前面,預設為 nil (代表換行後前面不加任何字)。假設 fill-prefix"… ",則換行後會在該列最前面加上 "… "

設定這個參數可以用 set-fill-prefixC-x .,要注意的是這個動作會將游標所在位置前的同列文字設為 fill-prefix,若在斷行處操作,則整列文字都會被採用。因此我建議透過 eval 方式設定:

M-: (setq fill-prefix "... ")

或乾脆寫進 .emacs


fill prefix 對於整理程式碼註解很有幫助,但對於一般文字也許會造成困擾。要取消 fill-prefix 只需要找個空行再設定一次即可 (minibuf 會顯示 "fill-prefix cancelled")

Long line mode

相較於 fill mode,long line mode 則是溫和的非破壞性操作,只是在顯示上調整成 fill mode 的效果 (soft newline),並不會真的插入換行 (hard newline):

M-x longlines-mode


但顯示與實際內容畢竟有差別,可以將真正的換行處以特殊符號表示以資識別:

M-x longlines-show-hard-newlines


在處理長文字列的顯示上,Emacs 提供了豐富的設定以應付各種需求。就我個人來說,對於一般的文章我習慣用 line wrap 型式顯式;而在編輯程式碼的時候,這種方式反而會破壞縮排的視覺效果,這時改用 truncation 就很適合了。至於 long line mode,我到現在還沒用過。當然,一般編輯程式碼的時候很少會出現超長的程式碼 (也許寫 gtk 程式時例外XD),閱讀上非常不便。非不得已的時候,我會想辦法在 80 columns 內先斷行 (以前留下來的習慣@@),非超出視窗不可的部份,就交給強大的編輯器來處理吧!

Reference


Posted in emacs, Note

二月 5, 2009
» 透過 Outline mode 編輯 Muse 文稿內容

前陣子在使用 muse 撰寫筆記的時候,總覺得要找標題不太方便,似乎也沒辦法透過 speedbar 顯示大綱,玩了半天就想到了 outline mode 這個好東西,剛好語法是一樣的,只要簡單配置一下就可以帶來很大的方便。測試了一下後加了一些設定:

(add-hook 'muse-mode-hook
	'(lambda ()
	   (outline-minor-mode t)))

(define-key muse-mode-map (kbd "<f5>")  'outline-up-heading)
(define-key muse-mode-map (kbd "<f6>")  'outline-backward-same-level)
(define-key muse-mode-map (kbd "<f7>")  'outline-forward-same-level)
(define-key muse-mode-map (kbd "<f8>")  'outline-next-heading)
(define-key muse-mode-map (kbd "<f12>") 'outline-toggle-children)
(define-key muse-mode-map (kbd "C-<f12>") 'outline-mark-subtree)

(define-key muse-mode-map (kbd "C-<f5>") 'outline-promote)
(define-key muse-mode-map (kbd "C-<f6>") 'outline-move-subtree-up)
(define-key muse-mode-map (kbd "C-<f7>") 'outline-move-subtree-down)
(define-key muse-mode-map (kbd "C-<f8>") 'outline-demote)

在進入 muse mode 的同時也啟用 outline minor mode,並設定一些快速鍵以便快速在各個標題之間定位。事後在調整內容次序時,還可以整個 subtree 一起調整,不必再笨笨地用 copy & paste 修改了。


Posted in emacs, Note

十月 19, 2008
» Blogging with Emacs

玩了幾個晚上,總算可以順利從 emacs 裡將文章送到 Blog 了。 自從開始用 emacs 後就一直改用 muse 在寫筆記和報告,設定好以後可以透過 LaTeX 轉換成 PDF 檔,相當方便。用習慣後也曾興起透過 muse 寫 Blog 的念頭,找了很多資料,也知道有很多人實作成功,但說真的要拿來用還是有點問題,主要還是 elisp 不熟,而且大部份的實作竟然都是直接在 emacs 裡寫 html 後送出去 (還有人推薦用 nxml);就算直接在後台寫也有方便的 wysiwyg editor,但 emacs 還有 muse 這個好東西,何必那麼苦命呢。

» Blogging with Emacs

玩了幾個晚上,總算可以順利從 emacs 裡將文章送到 Blog 了。

自從開始用 emacs 後就一直改用 muse 在寫筆記和報告,設定好以後可以透過 LaTeX 轉換成 PDF 檔,相當方便。用習慣後也曾興起透過 muse 寫 Blog 的念頭,找了很多資料,也知道有很多人實作成功,但說真的要拿來用還是有點問題,主要還是 elisp 不熟,而且大部份的實作竟然都是直接在 emacs 裡寫 html 後送出去 (還有人推薦用 nxml);就算直接在後台寫也有方便的 wysiwyg editor,但 emacs 還有 muse 這個好東西,何必那麼苦命呢。

目前的做法還是透過 xml-rpcweblogger,後來找到有人針對 wordpress 的修改,安裝上並不難,只需稍微自訂一下就可以使用。

首先必須透過 weblogger-setup-weblog 建立設定檔,可以有多組設定,每一組設定會有一個名字作為識別。要注意的是預設並不儲存密碼,往後傳送資料時必須手動輸入。如果想偷懶可以設定一下 weblogger-save-password,建立設定資料時就會自動儲存密碼。

開始撰寫文章時先 M-x weblogger-start-entry,會自動建立簡單的樣板,填入 Subject 與內容即可。需要加上 Category 的話,可以在 header section 手動加入一行 "Keywords: ",各分類之間以逗點分隔 (必須是 server 上存在的 Category 名稱)。目前似乎還不支援 Tag。發布文件的方式很簡單,熱鍵跟存檔一樣是 C-x C-s,也可以用 C-c C-c 存成 Draft。日後需要修改時也可以把文章抓回來修改後再回存,相當方便1

Weblogger 的使用不難,比較麻煩的是跟 muse 銜接的部份,研究了一晚找出需要的資料結構,原來是建構在 message-mode 之上,所以得先產生類似 message header 的資料。這部份我直接以 html style 為基礎,建立一個新的 muse style 來處理:

(muse-derive-style "my-blog" "html"
                   :suffix ".wp.html"
                   :header
"Subject: <lisp>(muse-publishing-directive \"title\")</lisp>
From: <lisp>(muse-publishing-directive \"author\")</lisp>
Keywords: <lisp>(muse-publishing-directive \"desc\")</lisp>
--text follows this line--
"
                   :footer ""
                   :browser   'find-file
                   :regexps `((100000 "!!more!!" 0 more) )
                   :functions '((more . muse-mywp-markup-more))
                   :strings '(
(image-link      . "<a target=\"_blank\" class=\"image-link\" href=\"%s\">
<img src=\"%s.%s\"></a>")
(url             . "<a target=\"_blank\" href=\"%s\">%s</a>")
(link            . "<a target=\"_blank\" href=\"%s\">%s</a>")
(link-and-anchor . "<a target=\"_blank\" href=\"%s#%s\">%s</a>")))

當然在 project 部份也要新增一個項目

(setq muse-project-alist
	'(("blog" ("~/Note/blog" )
		(:base "my-blog" :path "~/Note/blog/convert"))))

本來的想法是找出 muse 資料的產生方式,直接丟到 webloger buffer,但是一堆括號看到頭暈,所以用了比較笨的方式:先用 muse 轉換過,再呼叫修改過的 weblogger 函式將結果轉換出必要的資料結構,最後丟上 server。

修改這些其實還算簡單,但我有個需求是要同時送到多個地方備份用,這可要我命了…因為裡面一堆全域變數,從帳號設定對應到內部資料結構的部份又太複雜 (誰說用 functional language 不會有 side-effect…XD),所以我還是用最笨的方式,每次連線前先重置相關的全域變數:

(defun muse-blog-start-entry ()
  (unless weblogger-entry-ring
    (setq weblogger-entry-ring
      (make-ring weblogger-max-entries-in-ring)))
  (ring-insert weblogger-entry-ring '(("content" "")))
  (setq weblogger-ring-index 0)
  (unless weblogger-server-username
    (setq weblogger-server-username ""))
  (unless weblogger-server-password
    (setq weblogger-server-password ""))
  (weblogger-edit-entry (weblogger-entry-buffer-to-struct)))

(defun muse-blog-send-post (&optional blogConfName)
  "send current to blog"
  (unless blogConfName
    (read-from-minibuffer "Select a blog conf: " blogConfName))
  (setq weblogger-server-username nil)
  (setq weblogger-server-password nil)
  (setq weblogger-server-url nil)
  (setq weblogger-weblog-id nil)
  (weblogger-select-configuration blogConfName)
  (weblogger-start-entry)
  (set-buffer-modified-p t)
  (weblogger-save-entry t)
  (bury-buffer))

(defvar muse-blog-conf-name-alist nil)

(defun muse-blog-publish ()
  (interactive)
  (when muse-blog-conf-name-alist
    (mapcar 'muse-blog-send-post muse-blog-conf-name-alist)))

使用前需要先設定 muse-blog-conf-name-alist,內容是一個字串的 list,就是先前在 weblogger 中建立的 profile 名稱,發布文章時就會丟到所有在清單中的 Blog 位置。

muse 文稿的部份,要注意的是利用了三個 directives:title、author 與 desc,分別對應到 message header 中的 Subject、From 與 Keywords。以本文為例,開頭的前幾行如下:

#title Blogging with Emacs
#author letoh
#desc note, emacs

產生出來的 header 如下:

Subject: Blogging with Emacs
From: letoh
Keywords: note, emacs
–text follows this line–

到這樣已經相當好用了,同一份 muse 文件除了生成 LaTeX / ConTeXt 文稿或 PDF 外,也可以發布到 Blog 上,當然產生 static page 或整理起來變成 Wiki 更不是問題。只剩一個小小的缺憾,就是還沒把 blogger 分舵加進來,因為 blogger 已經不用 xmlrpc,改用 Blogger Data API 了,這兩天再努力看看吧。有任何建議也歡迎指教。


1. 實際使用後發現抓回來的文章如果包含 <!--more--> (wordpress 設定摘要位置用的記號),只會抓回摘要的部份;此時若回存會把文章後半部份蓋掉XD 修改方法還在研究中…


Posted in emacs, Note

» Blogging with Emacs

玩了幾個晚上,總算可以順利從 emacs 裡將文章送到 Blog 了。 自從開始用 emacs 後就一直改用 muse 在寫筆記和報告,設定好以後可以透過 LaTeX 轉換成 PDF 檔,相當方便。用習慣後也曾興起透過 muse 寫 Blog 的念頭,找了很多資料,也知道有很多人實作成功,但說真的要拿來用還是有點問題,主要還是 elisp 不熟,而且大部份的實作竟然都是直接在 emacs 裡寫 html 後送出去 (還有人推薦用 nxml);就算直接在後台寫也有方便的 wysiwyg editor,但 emacs 還有 muse 這個好東西,何必那麼苦命呢。

十月 16, 2008
» Emacs 發布測試

test for blogging with emacs
透過 emacs 來寫 blog

Todo:

  • fetch entry 後,延伸閱讀以後的資料會抓不到 (也許是透過 rss/atom 抓 description 而已),此時再存檔的話,當初沒抓下來的資料就會變成光了
  • 拿掉 auto-fill-mode
  • 整合 muse (目前看到有日本人實作過),這樣等於間接把 local preview 做好
  • multi-server configuration
  • categories auto-completion (以前用 python 寫過,但現在得用 elisp 寫…囧rz)
  • 自訂 ping-slug (印象中在 api 裡有看到)

Posted in ACG, emacs, Note

八月 31, 2008
» 統計檔案大小

隨手寫了一個 script,用來統計檔案大小

#!/usr/bin/perl -p
BEGIN { my $total = 0; }
/(\d+)/; $total += $1;
END { print “Total: $total\t” . ($total / 1024) . “M\n”; }

存成 ts 後可以搭配 duls -s 使用,或者是輸出為每一行開頭有數字的任何指令,例如 seqnl

$ seq 5 | ts
$ ls / | nl | ts

當然這只是好玩而已,沒什麼實際用途

突然想到這幾年陸續接觸了幾種不同的程式語言,包括平日使用最多的 python。但日常使用其實很少出現複雜的應用,大多數情況用 perl 處理起來還是簡潔多了 (雖然這個 perl script 寫得有點像 awk script…)

最後附一下 awk 版:

BEGIN { total = 0 }
total += $1
END { print “Total:”, total,”\t”,total/1024,”M” }


» 統計檔案大小

隨手寫了一個 script,用來統計檔案大小

八月 24, 2008
» Lilytan v0.1 released!!

今天花了將近四小時接生,終於把 Lilytan 生出來啦!

» Lilytan v0.1 released!!

今天花了將近四小時接生,終於把 Lilytan 生出來啦!

身為一個重度 CLI 使用者 (自稱),有一個穩定好用的 terminal emulator 在手是相當重要的。以前習慣使用的是 mrxvt,但可惜的是不支援 UTF-8,在操作日文檔案時相當不便;後來有一陣子改用 xfce 內附的 Terminal 程式,大致上不錯,但常有一些小問題很難搞定,例如按鍵與 termcap 相關設定,使得部份程式的快速鍵會有問題。前陣子突然冒出一堆以 libvte 來實作的 terminal,試用之後發現有兩個還不錯,分別是 evilvtelilyterm

目前比較慣用的是 lilyterm,對我來說只要滿足:

  1. UTF-8 支援
  2. 編碼設定
  3. Tab 支援
  4. emacs 與 vim 的按鍵正常

就是一個不錯的 terminal emulator。基本上以 libvte 的話就可以滿足大部份需求,但我選擇這兩套還有一個重大原因:開發很活躍,而且原作者很好找,bug report 方便 :D

今天在閒聊時扯到 OS-tan,催生 Lilytan 的構想便油然而生。把塵封已久的 tablet 拿出來拍拍灰塵,大概半個小時就把草圖畫好。不過既然都動手了,還是決定認真練習一下在 GIMP 中繪製 CG 的方法。經過將近三個小時的 ”refactoring” 後,把一些部件拆開重新調整一下、製作 mask、加上陰影,看起來色彩豐富多了。不過因為很少畫 Q 版人物造型,所以整體看來還有進步空間…(藉口 藉口 藉口…)

Lilytan 的設定中其實有個鍵盤配件,而且也畫好了。但因為今天是第一次登場,所以來點殺必死,以泳裝造型亮相

大家要多多支持 Lilytan 喔!


六月 22, 2008
» VirtualBox 1.6.2

由於 sane 不支援我的 scanner,為了試試透過 virtualbox 存取 scanner,這兩天在家把新版裝起來玩…

» VirtualBox 1.6.2

由於 sane 不支援我的 scanner,為了試試透過 virtualbox 存取 scanner,這兩天在家把新版裝起來玩…

根據網路上看到的使用心得,新的 1.6.2 版似乎在 usb 的支援上增強不少,但為了在 guest OS 裡抓到 usb device 也費了不少工夫,一度還以為只有 windows 版才有改進…搞了一個下午才順利偵測到 scanner

網路沒設,主要是處理 usb 的部份,記錄一下大概的步驟:

  1. 新增 usbusers 群組,把自己的帳號加進去
  2. 修改 /etc/rc.d/rc.S 中 mount usbfs 的命令,加上 -o devgid=<gid of usbusers>,devmode=664 (我用的 distro 是 Slackware)
  3. 修改 /etc/udev/rules.d/65-permissions.rules,加一行 SUBSYSTEM=="usb_device", GROUP="usbusers", MODE="0664"
  4. 在 virtualbox 中加入 usb device filter (vid 和 pid 可以透過 lsusb 查到)

處理完以後啟動 guest OS 就可以抓到 device 了,為了證明可以啟動 scanner,掃張圖來看看


六月 20, 2008
» libreadline.so.5: undefined symbol: PC

這兩天在 slackware 12.1 環境編譯一個有用到 readline 的程式,可是搞了老半天編譯好的程式都沒有 readline 跟 history 的功能。測試一下發現會出現 undefined symbol 的訊息,按照 maillist 上提供的指令 ldd -d -r /usr/lib/libreadline.so.5.2 出現這些訊息: undefined symbol: PC (/usr/lib/libreadline.so.5.2) undefined symbol: UP (/usr/lib/libreadline.so.5.2) undefined symbol: BC (/usr/lib/libreadline.so.5.2) undefined symbol: tgetflag (/usr/lib/libreadline.so.5.2) undefined symbol: tgetent (/usr/lib/libreadline.so.5.2) undefined [...]

» libreadline.so.5: undefined symbol: PC

這兩天在 slackware 12.1 環境編譯一個有用到 readline 的程式,可是搞了老半天編譯好的程式都沒有 readline 跟 history 的功能。測試一下發現會出現 undefined symbol 的訊息,按照 maillist 上提供的指令 ldd -d -r /usr/lib/libreadline.so.5.2 出現這些訊息:

undefined symbol: PC (/usr/lib/libreadline.so.5.2)
undefined symbol: UP (/usr/lib/libreadline.so.5.2)
undefined symbol: BC (/usr/lib/libreadline.so.5.2)
undefined symbol: tgetflag (/usr/lib/libreadline.so.5.2)
undefined symbol: tgetent (/usr/lib/libreadline.so.5.2)
undefined symbol: tputs (/usr/lib/libreadline.so.5.2)
undefined symbol: tgoto (/usr/lib/libreadline.so.5.2)
undefined symbol: tgetnum (/usr/lib/libreadline.so.5.2)
undefined symbol: tgetstr (/usr/lib/libreadline.so.5.2)

查了一下發現這些東西在 libtermcap 裡,比較簡單一點的方法就是修改 Makefile 或 build script,在 linking 時加上 libtermcap 即可


六月 15, 2008
» Device driver check page

Debian GNU/Linux device driver check page 在 kerneltrap 上看來的網站,把 lspci -n 的結果丟進去即可,相當方便

» Device driver check page

Debian GNU/Linux device driver check page 在 kerneltrap 上看來的網站,把 lspci -n 的結果丟進去即可,相當方便

A Feedjack powered Planet
A Django site.