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に走るのも無理無いな。