TECO
去年の暮れに家の中を整理してて、女房に同軸ケーブルやらLANケーブル類をいっさいかっさい処分された。 5年も使っていないから今後使う事無いでしょってのが理由。女房でもLRUのアルゴリズムを 知ってるなんて普遍的なアルゴリズムですな。それとも、使ってないものに眼を付けてGCするんだから、 お掃除ロボットかもよ。 その証拠に、古文書の処理をオイラに振ってきた。大事な物が混じっている事を期待してんだな。 生憎、へそくりなんて有りませんってば。
好々爺の部屋 (4) - TECO なんて言う印刷物が出てきた。本人が印刷した記憶無いんだけど、 誰かに貰ったのかな。読んでみたら面白い。TECOってemacsの祖先らしいです。それは知らなかったぞ。
早速Web調べ。tecoに再挑戦(retry)する 方がおられた。こういう古いのを再生するの楽しいよね。ソースは無いか?
FreeBSDではportsになってたけど、土台のvboxがCPU資源を食い潰す動きをするんで、 fedoraでやってみる。さすがに、fedoraでもウブでもパッケージには登録されていない。 ソースにぴったりのが置いてあった。 Text Editor and COrector
Linux TECOを落としてきた。展開するとbinが有ったけど、srcも付属してたんで、コンパイル に挑戦。
[sakae@fedora ~]$ cd tecoc0398/src/ [sakae@fedora src]$ make -f makefile : gcc -O -DLINUX -o genclp genclp.o genclp make: genclp: コマンドが見つかりませんでした makefile:59: recipe for target 'clpars.h' failed make: *** [clpars.h] Error 127
genclpを作ったのに見つからないとは、これいかに、とか思ったぞ。多分、この コマンドを使って、ソースを生成してるんでしょうって事で手打ち。そして コンパイルの継続
[sakae@fedora src]$ ./genclp [sakae@fedora src]$ make -f makefile gcc -O -DLINUX -c -g -o zlinux.o zlinux.c gcc -O -DLINUX -s -o tecoc tecoc.o baksrc.o ......
文句を言われたけど、tecocが出来上がった。FreeBSDのpkg-messageの説明によると、この 親玉コマンドにオプションを与えて、tecoを起動するらしい。
資料を参考に、超簡単な使い方
[sakae@fedora src]$ ./tecoc *iabc ; insert def ghi $$ ; end *.=$$ ; check cp 15 *j$$ ; cp to begin buffer *.=$$ 0 *v$$ ; view line abc *ewzzz$$ ; define output file *p$$ ; punch out ?NFI No file for input *ex$$ ; exit tecoc
今度は、読み込んで表示
[sakae@fedora src]$ ./tecoc *erzzz$$ ; define input file *y$$ ; yank(read) *j3t$$ ; cp to begin buf and 3 line type abc def ghi *ex$$
あれ、修正とかはどうやる? 資料嫁。正統派は、doc/teco.docって言う長ったらしい テキストを読む。そのショートカットバージョンは、wchart.txtだ。たった212個の コマンドが羅列されている。
そして、それは、テキストの修正だけじゃなくて、プログラムにも変身するんだな。 例に有った、πを求めるtecoのコマンド。
! pi2c.tec ! GZ0J\UNQN"E 40UN ' 0UH 1UV HK QN< J BUQ QN*10/3UI QI< \+2*10+(QQ*QI)UA 0L K QI*2-1UJ QA/QJUQ QA-(QQ*QJ)-2\ 10@I// QI-1UI > QQ/10UT QH+QTUW QW-9"E QV*10+QWUV | QW-10"E 0UW QV+1UV ' .UP QV\ QP+1,.T QP,.K QW+10UV ' QQ-(QT*10)UH > EX
こいつを、喰わせてやると、みんな大好きパイを開陳してくれる。ありがたく拝むんだぞ。 このパイ計算方法は、spigot algorithm digits pi と言うらしい。 Spigot アルゴリズムに解説が有った。 こつこつアルゴリズム(Spigot Algorithm) には、haskell版も有るぞ。
[sakae@fedora ~]$ tecoc0398/bin/tecoc mung pi2c.tec 100 314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706[sakae@fedora ~]$
import Data.Char piG3 :: [Integer] piG3 = g(1,180,60,2) where g(q,r,t,i) = let (u,y)=(3*(3*i+1)*(3*i+2),div(q*(27*i-12)+5*r)(5*t)) in y : g(10*q*i*(2*i-1),10*u*(q*(5*i-2)+r-y*t),t*u,i+1) main :: IO () main = putStrLn $ map (\d -> chr $ 48 + fromIntegral d) pi where pi = take 100 piG3
[sakae@fedora ~]$ runhugs pi.hs 3141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067
libの下には、有用なスクリプトが集められている。ああ、この当時は、マクロって 呼んでいたんだな。
dir.tecとかdir.tesってのが有る。前者は、本当にマクロだけ(に圧縮したやつ)のもの、 後者は、それにコメントを入れて、理解し易くしたもの。当時は、メモリーが貴重だったので、 こんな暗号みたいなマクロがまかり通っていたんだ。理解出来ないのは、お前の頭が悪いって 事にされたんだな。
これに意義を唱えたのは、かのRMS。こんなマクロよりLispの方がよっぽど人に優しいぞ。 だったら、Lisp語でマクロを書けるようにしちゃえ。彼が頑張って、emacsが生まれたとな。 (最初にteco語でemacsを書いたけど、怒り心頭Lisp語で書き直したの真相らしい)
tecoc 解体
何時もの例に倣って、解体ショーの始まり。でも、普通にコンパイルしたやつにdebug情報が 付いていない。makefileの中でstripでもしてるんかと思ったら、それもやっていない。
はてはてと思って、精査してみると、リンカーコマンドに不審なオプションを指定してる。
-s --strip-all Omit all symbol information from the output file.
また一つ無駄知識が増えましたなあ。ってんで、下記のようにレシピを整えたよ。無駄知識と 言えば、makefileとMakefileでどちらが強いか。今回、初めて知った。何事も経験が大事。
[sakae@fedora src]$ diff makefile Makefile 30c30 < COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) -c -g --- > COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) -c -O0 -gdwarf-2 -g3 54c54 < ${LINK.c} -s -o $@ tecoc.o ${OBJECTS} ${TERMOBJS} --- > ${LINK.c} -o $@ tecoc.o ${OBJECTS} ${TERMOBJS}
そして、コマンド待ちになった所。
(gdb) bt #0 0xb777bbe8 in __kernel_vsyscall () #1 0xb7680ee3 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84 #2 0x08054d78 in ZChIn (NoWait=0) at zlinux.c:170 #3 0x08051e8b in FirstChar () at readcs.c:219 #4 0x0805222b in ReadCS () at readcs.c:392 #5 0x08048e44 in main (argc=1, argv=0xbff78cb4) at tecoc.c:718
少し、流れを追ってみる。tecoc.c/mainの中はあっさりしてる。
699int _Cdecl main(argc, argv) /* _Cdecl defined in ZPORT.H */ 700int argc; 701char **argv; 702{ 708 Init(argc, argv); /* initialize TECO */ 717 FOREVER { /* loop forever */ 718=> ReadCS(); /* read command string */ 719 CBfPtr = CBfBeg; /* initialize command string ptr */ 720 CmdMod = '\0'; /* clear modifiers flags */ 721 EStTop = EStBot = /* clear expression stack */ 722 LStTop = LStBot = /* clear loop stack */ 723 MStTop = -1; /* clear macro stack */ 724 ExeCSt(); /* execute command string */ 725 } 726}
708行目で、引数の処理やらターミナルの準備、メモリーの確保等を行った後、永久ループ内で 、718行目でコマンドを読み込み、724行目で実行してるだけ。このループからの脱出は、 ローレベルな終了コマンドをから、TAbortを呼んだ時に実施される。素直な作りである。
ああ、大事な事を忘れていた。Initの最後で、clparsをコマンドバッファーにセットして、 ExeCStを呼んでいる。このclparsってのは、
/* * This file was created by the GENCLP program. * It should not be edited. To make changes, * change the CLPARS.TES file, then squeeze * CLPARS.TES to produce the CLPARS.TEC file, * then run GENCLP to produce this file. */ #if USE_ANSI_CLPARS... #else char *clpars[] = { "!CLPARS.TEC V2.3 14-May-2004 Tom Almy, Pete Siemsen, and Mark Hen", "derson![F0,0XF[90,0X9[00,0X0[20,0X210U0[0\004\030U0[00\030EDU0[0E", : "2]0]9]F0U.10U.L0U40UY0,0X.T\033\033" }; #define CLPARS_LEN 4301 #define CLPARS_LINES 84
コメントに有るように、tecocをコンパイル中に作成された、genclpによって、clpars.tesから 自動生成されたteco語である。それを、何喰わぬ顔をして実行してる。これが何をやってるか? コマンドラインの解析等のようだ。
実際のコマンド振り分けは、execst.c/ExeCStにて行っている。
15DEFAULT ExeCSt() /* execute command string */ 16{ 17 DEFAULT status; 18 static DEFAULT (*FArray[])(VVOID) = { 19/*NUL*/ ExeNul, /* ^A*/ ExeCtA, /* ^B*/ ZExCtB, /* ^C*/ ExeCtC, : 37/* H */ ExeH, /* I */ ExeI, /* J */ ExeJ, /* K */ ExeK, : 45/* h */ ExeH, /* i */ ExeI, /* j */ ExeJ, /* k */ ExeK, : 82/*253*/ ExeIll, /*254*/ ExeIll, /*255*/ ExeIll, /*256*/ ExeIll 83 }; : 92 status = (*FArray[*CBfPtr])(); /* execute command */
ASCIIの全コードに相当するコマンドテーブルを持ってて、対応するコマンドに ジャンプする構造。コマンドは、大文字、小文字で同一になっている。
(gdb) p CBfPtr $3 = (charptr) 0x8068070 "iabcdefg\r\n\033\033"
一行分を入力しようとした。素直にコマンドバッファに入力文字列が入っている。 そして、コマンドが振り分けられて、
17DEFAULT ExeI() /* execute an I command */ 18{ 19 unsigned char InChar; 20 21 DBGFEN(1,"ExeI",NULL); 22=> if (EStTop > EStBot) { /* if numeric argument */ 23 if (GetNmA() == FAILURE) { /* get numeric argument */ 24 DBGFEX(1,DbgFNm,"FAILURE, GetNmA() failed"); 25 return FAILURE; 26 } :
どうやらコマンドに前置される引数は、別の所で処理されてる感じ。(後で追ってみる) 後は、この関数の中で、マクロがもしあれば処理、通常の挿入も処理してる。
冒頭に数字の引数が付く場合はどうなるか?
(gdb) p CBfPtr $3 = (charptr) 0x8068070 "30t\033\033"
30行文表示してねってやつ。
普通に解析されて、
17DEFAULT ExeDgt() /* execute a digit command */ 18{ 19 LONG TmpLng; 20 21 DBGFEN(1,"ExeDgt",NULL); 22=> if ((Radix == 8) && (*CBfPtr > '7')) { /* if bad octal digit */ 23 ErrMsg(ERR_ILN); /* ILN = "illegal number" */ 24 DBGFEX(1,DbgFNm,"FAILURE, bad octal digit"); 25 return FAILURE; 26 } : 58=> return PushEx(TmpLng, OPERAND); 59}
数字列をパースして、それをpush。下記はpush内容。
(gdb) p *$ $6 = {Elemnt = 30, ElType = 0}
もう一度評価すると、表示コマンドに行き着き、そこで、スタックから値をポップして、 指定の行数を表示してる。非常に素直な作りだ。
わざわざ、数字引数の挙動を調べる事は無かったねって、今気が付いた。ExeCSt内に 宣言されてる、コマンドテーブル(FArray)内に、ExeDgtってのが登録されてるよ。まだまだ、 修行が足りないな。
上でOPERANDなんてのが出てきたので、tecoc.hを探ってみると
#define OPERAND 0 /* means element is an integer */ #define OPERATOR 1 /* means element is + - * / & or | */
こんな定義がしてあった。OPRATORにも注目だな。
マクロ
tecocはマクロが売りらしい。ようするにプログラミング出来るって事。パイで証明済み。 qレジスタってのが用意されてるとな。そのレジスタ名は、1文字で表されていて、0-9、a-zまで36個 あるらしい。
qレジスタって言うけど、平たく言ったら、変数だ。1文字変数。1文字変数で Tiny BASICなんてのを思い出したぞ。
tiny basicには、配列が有ったそうな。tecoには無い。嘆くな青年、テキストバッファーと 言う広大なバッファーが有るじゃないかと、好々爺は言う。
で、このqレジスタに対するアクセスのしかたはどうよ? ちょいと調べてみる。
[sakae@fedora src]$ ./tecoc *er../doc/wchart.txt$$ *y$$ *j<@s/q-reg/;v>$$ ^EGq (match char) match contents of q-register ^EQq (string build char) use contents of q-register q ^EUq (string build char) use ASCII code in q-register ^Uq put string into q-register :^Uq append string to q-register n^Uq put ASCII character "n" into q-register n:^Uq append ASCII "n" to q-register ^Z size of text in all q-registers, plus command line n%q add n to q-register q, return the result *q immediate mode: save last command string in q-register q E%q write q-register to file EQq read from file into q-register Gq get string from q-register into edit buffer :Gq type text in q-register q Mq execute commands in q-register Qq number in q-register q :Qq size of text in q-register q nUq put n into q-register q nXq put n lines into q-register q m,nXq put character m through n into q-register q n:Xq append n lines to q-register q m,n:Xq append characters m through n to q-register q [q push q-register q onto q-register stack ]q pop q-register stack into q-register q
上記は、コマンドリストから、tecoのマクロを使って、q-regをgrepしたものだ。上記で小文字のq ってなってる所が、具体的には一文字の変数名に置き換えて使う。数字でも文字列でも入れて おけるのはBASICと同じだな。
マクロでのサーチ。何回ヒットするか分からないので、マクロ実行を三角括弧でくくって ループさせる。マクロでは、サーチのsコマンドの前にあっとマークを置くのがお約束らしい。 そして検索文字は/等、検索文字に含まれない文字で囲むとな。
サーチが成功すると、true(非ゼロ)な値が返るらしい、falseなら次の;でループを脱出。 検索成功なら、脱出せずに次のvで1行表示ってことらしい。
幸い今回のマクロは短かったから、直打ち出来たけど、長いマクロの場合は、別ファイル(例えば、 grep.tec)なんてファイルに書いておいて、
*eqggrep.tec$$ *mg$$
のように、eqコマンドを使ってgレジスタにマクロの内容を取り込み、mコマンドでgレジスターを 指定して、マクロを実行させる事が出来る。なお、マクロ内では、スペースやCRLFは無視 されるので、見やすく整形すると良い。
libの下を漁っていたら、dirの内容を表示するマクロが有ったので実行してみた。
[sakae@fedora lib]$ ../bin/teco *eqadir.tec$$ *ma$$ change.tec change.tes checkqr.tec date.tec date.tes detab.tec detab.tes dir.tec dir.tes lowcase.tec makprnt.tec makprnt.tes squ.tec squ.tes sub.tec tst.tec tstbsl.tec tstcss.tec tstequ.tec tsto.tec tstqr.tec tstsrc.tec unsqu.tec unsqu.tes upcase.tec
ちゃんと表示されてる。
[sakae@fedora lib]$ lv dir.tes !dir.tes-display directory! [0 [1 [2 ! save Q-regs 0-2 ! .u0 ! remember where we are ! zj ! jump to end of buffer ! .u1 ! remember old end of buffer ! 0u2 ! zero column count ! @^u1%*.*% ! default is get dir of everything ! q1"n ! is buffer not empty? ! 80-q1"g ! is buffer length < maxpath? ! 0,q1x1 ! assume edit buf = filespec ! ' ! endif ! ' ! endif ! en^eq1 ! preset wildcard lookup ! q1j ! jump to end of buffer ! < ! <build filename display ! :en^[; ! return filename, break if none ! g* ! put filename into edit buffer ! (1%2)-5"e ! is fifth and final column? ! 13@i%% ! insert <CR> ! 10@i%% ! insert <LF> ! 0u2 ! reset column count ! | ! else ! 15+^S< ! <do column mod 15 times ! 32@i%% ! insert <SPACE> ! > ! > ! ' ! endif ! > ! > ! q1,zt ! display what we built ! q1,zk ! kill what we built ! q0j ! jump to where we were ! ]2 ]1 ]0 ! restore Q-regs 0-2 ! ^[^[
平気でコントロールキャラクターが使われているので、表示するには不向きかも。 これじゃ、RMSがLispに走るのも無理無いな。