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

readme

Gnu の誕生まで

TECO and Emacs