m4

白菜

とある日、市場へ行ったら、丁度農家の人が白菜を並べていた。中玉が150円、大玉が200円。 迷わず、大玉をGet。

韓国ではキムチの漬け込みの時期。中国では? 台北の故宮博物館に、翡翠を彫刻した見事な白菜が飾ってあったから、馴染みの野菜だろう。餃子の具材にでもするのかな。それにしても食べ切れないと思うけど。

買ったやつは、大きすぎて冷蔵庫に入らず。なるべく早いうちに食べよう。一番のお勧めは、蒸しておいて、おかかを降って食べるやつ。いわゆるお浸しね。

それから、貧乏鍋ってやつ。鍋に白菜とサバ缶を入れて煮たやつ。これで、白菜を大量消費出来て、安くて栄養満点。貧乏人にぴったりだ。

rdoc

前回は、perldocなんてのを思いだされてしまった。じゃrubyは?

[sakae@fb /usr/local/lib/ruby/2.7]$ rdoc erb.rb
uh-oh! RDoc had a problem:
Permission denied @ dir_s_mkdir - doc

run with --debug for full backtrace

これで、man風に確認出来るかと思っていたら違った。

[sakae@fb /usr/local/lib/ruby/2.7]$ rdoc -o /tmp/doc erb.rb
Parsing sources...
100% [ 1/ 1]  erb.rb
 :
  Total:      23 (0 undocumented)
  100.00% documented

  Elapsed: 0.4s

こんな風にやるみたい。で、出来上がったものを確認。

[sakae@fb /tmp/doc]$ w3m index.html

Pages Classes Methods
[                    ]

Class and Module Index

  • ERB
  • ERB::DefMethod
  • ERB::Util
   :

今風にWEBからのアクセスになるのね。cgiも追加だ。

[sakae@fb /usr/local/lib/ruby/2.7]$ rdoc -o /tmp/doc cgi.rb
Parsing sources...
100% [ 1/ 1]  cgi.rb
 :

index.htmlでは、CGIのやつしか見えなくなった。追加する時は、なんかオプションが必要なのかな? まあ、雰囲気が分ったから良としておこう。

at linux

今迄OpenBSD主体でautom4teしてきたので、リナではどうなるかやってみた。code.shは、ログを取ってからm4を起動してる部分を抜き出したものだ。一行野郎になってるので、見易く分解してみた。

sakae@deb:/tmp/$ cat code.sh | tr ' ' '\n'
/usr/bin/m4
--nesting-limit=1024
--gnu
--include=/usr/share/autoconf
--debug=aflq
--fatal-warning
--debugfile=autom4te.cache/traces.0t
--trace=AC_CANONICAL_BUILD
  :
--trace=sinclude
--reload-state=/usr/share/autoconf/autoconf/autoconf.m4f
--undefine=__m4_version__
configure.ac > autom4te.cache/output.0t

同じ事をFreeBSDでやってみると、m4の代わりに/usr/local/bin/gm4が使われていた。

FreeBSDには、オリジナル(OSと一緒にリリース)版も有り、それは勿論、/usr/bin/m4だ。こちらは、OpenBSDから頂いてきたものだ。autoconfのパッケージャーの方は、労力を削減する為、All GNUで固めたって事だ。色々有って、楽しいな。リナ寄りのFreeBSD。OpenBSDは、独立独歩である。

at OpenBSD

一応、対比って事で

/usr/bin/m4 -g -D__gnu__
-I/usr/local/share/autoconf-2.69
-daflq
-oautom4te.cache/traces.0t
-tAC_CANONICAL_BUILD
  :
-tsinclude
/usr/local/share/autoconf-2.69/m4sugar/m4sugar.m4
/usr/local/share/autoconf-2.69/m4sugar/m4sh.m4
/usr/local/share/autoconf-2.69/autoconf/autoconf.m4
configure.ac > autom4te.cache/output.0t

顕著な違いは、autoconf.m4fで、まとめたものを使ってるリナに比べて、分解して与えてる事があるな。

最小の configure.ac

長いとわけが解らなくなるので、うんと短かいやつ(これ自体に意味は無い)。

AC_INIT([el], [1.0], [foo@java.jp])
AC_CHECK_FUNCS(arc4random_buf)

これを使って実行。

ob$ autom4te -d -v -l autoconf -o z configure.ac >LOG 2>&1

出来上がったもののコメントを表示。

ob$ egrep '^## .* ##$' z
## -------------------- ##
## M4sh Initialization. ##
## -------------------- ##
## --------------------- ##
## M4sh Shell Functions. ##
## --------------------- ##
## ------------------------ ##
## Autoconf initialization. ##
## ------------------------ ##
## --------- ##
## Platform. ##
## --------- ##
## ----------- ##
## Core tests. ##
## ----------- ##
## Cache variables. ##
## Output variables. ##
## File substitutions. ##
## confdefs.h. ##
## -------------------- ##
## Main body of script. ##
## -------------------- ##

Mainの部分ではCCの検出等重要なチェックが行われている。そして、やおら

for ac_func in arc4random_buf
do :
  ac_fn_c_check_func "$LINENO" "arc4random_buf" "ac_cv_func_arc4random_buf"
if test "x$ac_cv_func_arc4random_buf" = xyes; then :
  cat >>confdefs.h <<_ACEOF
#define HAVE_ARC4RANDOM_BUF 1
_ACEOF

fi
done

configure.acの2行目が出現する。トレースログは

m4trace:configure.ac:2: -1- AH_OUTPUT([HAVE_ARC4RANDOM_BUF], [/* Define to 1 if you have the `arc4random_buf\' function. */
@%:@undef HAVE_ARC4RANDOM_BUF])
m4trace:configure.ac:2: -1- AC_SUBST([CC])
m4trace:configure.ac:2: -1- AC_SUBST_TRACE([CC])
m4trace:configure.ac:2: -1- m4_pattern_allow([^CC$])
 :

これまた、複数のマクロが実行されてる事が見て取れる。

m4 for debug

m4に潜って行って、どんな事が行われているか、観光しよう。

vbox$ make
lex  -o tokenizer.c tokenizer.l
yacc -d -o parser.c parser.y
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c eval.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c expr.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c look.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c main.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c misc.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c gnum4.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c trace.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c tokenizer.c
cc -g -O0 -DEXTENDED -I.  -MD -MP  -c parser.c
cc   -o m4 eval.o expr.o look.o main.o misc.o gnum4.o trace.o tokenizer.o parser.o -lm -lutil

OpenBSDのMakefileは非常に綺麗になってる。それに対してFreeBSDのそれは、随分と回りくどい事になってる。手軽にいじるならOpenBSDがやはり一番良い。

PSD.doc

m4のソースに同梱して、歴史的文書があった。こういうの読むのが楽しい。下記は、その冒頭部分。

vbox$ man  -Tascii ./m4.ms | col -b >/tmp/m4.txt

テキストに変換出来れば、後はググルのAIにお任せさ。

The M4 Macro Processor Brian W. Kernighan Dennis M. Ritchie

       M4 is a macro processor available on and GCOS.  Its primary use has
       been as a front end for Ratfor for those cases where parameterless
       macros are not adequately powerful.  It has also been used for
       languages as disparate as C and Cobol.  M4 is particularly suited for
       functional languages like Fortran, PL/I and C since macros are
       specified in a functional notation.

ちょっと、翻訳例をば。

M4の基本的な操作は、入力を出力にコピーすることです。として
入力は読み取られますが、各英数字の「トークン」(つまり、
文字と数字)がチェックされます。それがマクロの名前である場合、
マクロの名前はその定義テキストに置き換えられ、結果は
文字列は再スキャンされる入力にプッシュバックされます。マクロは
引数を使用して呼び出されます。この場合、引数が収集され、
定義テキストの適切な場所に置き換えられます
再スキャンされました。

run m4

簡単なマクロの例。

vbox$ cat aaa.m4
define(abc, `HOGE')dnl
I am M4
# hello
abc is from m4.macro
That's abc_to_HOGE

それをトレース付きで実行してみる

vbox$ m4 -tabc aaa.m4
I am M4
# hello
m4trace: -1- abc -> `HOGE'
HOGE is from m4.macro
That's abc_to_HOGE

-tabc を付けないと、m4traceの行は出現しない。 上記は、マクロ定義(define)と、変換対象の文が混在してる。普通は、マクロ定義だけを集めたファイルを別に作り、変換対象のファイルと分離するだろう。

vbox$ cat def.m4
define(swap, `$2 and $1')dnl
vbox$ cat smpl.txt
`swap(world, hello)' output is swap(world, hello)

変換規則を定義した def.m4と、その対象になるファイルを用意。

vbox$ m4 def.m4 smpl.txt
swap(world, hello) output is hello and world

それを適用。サンプル文書中で、バッククォートとクォーテーションで囲んでいる部分は、それをそのままの文字列として扱って欲しいというお願いだ。そうしないと、swapがその場で実行されちゃうからね。まるでLispに範を取った面持ちだ。

vbox$ m4 -dV -tswap def.m4 smpl.txt
m4trace:def.m4:1: -1- id 1: define ...
m4trace:def.m4:1: -1- id 1: define(`swap', `$2 and $1') -> ???
m4trace:def.m4:1: -1- id 1: define(...) -> `'
m4trace:def.m4:2: -1- id 2: dnl ...
m4trace:def.m4:2: -1- id 2: dnl -> ???
m4trace:def.m4:2: -1- id 2: dnl -> `'
m4trace:smpl.txt:1: -1- id 3: swap ...
m4trace:smpl.txt:1: -1- id 3: swap(`world', `hello') -> ???
m4trace:smpl.txt:1: -1- id 3: swap(...) -> `hello and world'
swap(world, hello) output is hello and world

みんな入りのトレースオプションを指定してみた。もうLispを扱っているとしか思えないな。面白くなってきたぞ。

with gdb

ソースをざっと見してたら、dodefine なんてのをmain.cに見付けた。とっかかりとしてBPをおいてみる。(C-x C-a C-b gud-break)

(gdb) b main
Breakpoint 1 at 0x7b1a: file main.c, line 173.
(gdb) r aaa.m4
Starting program: /tmp/run/m4 aaa.m4

Breakpoint 1, main (argc=2, argv=0xcf7db3d4) at main.c:173
173             if (pledge("stdio rpath wpath cpath tmppath proc exec", NULL) == -1)
(gdb) b dodefine
Breakpoint 2 at 0x18bbbafa: file eval.c, line 608.
(gdb) c
Continuing.

Breakpoint 2, dodefine (name=0x7af51000 "abc", defn=0x7af51004 "HOGE")
    at eval.c:608
608             if (!*name && !mimic_gnu)
(gdb) bt
#0  dodefine (name=0x7af51000 "abc", defn=0x7af51004 "HOGE") at eval.c:608
#1  0x18bbad0e in expand_builtin (argv=0x47f7a010, argc=4, td=1026)
    at eval.c:151
#2  0x18bba8de in eval (argv=0x47f7a010, argc=4, td=1026, is_traced=0)
    at eval.c:111
#3  0x18bbf07d in macro () at main.c:461
#4  0x18bbdfa6 in main (argc=0, argv=0xcf7db3d8) at main.c:259

その後、少しトレースしてみると、マクロ名はハッシュで検索を素早く出来るようにしてるっぽい。look.cあたりでの担当だな。

eval.c

/*
 * eval - eval all macros and builtins calls
 *        argc - number of elements in argv.
 *        argv - element vector :
 *                      argv[0] = definition of a user
 *                                macro or NULL if built-in.
 *                      argv[1] = name of the macro or
 *                                built-in.
 *                      argv[2] = parameters to user-defined
 *                         .      macro or built-in.
 *                         .
 *
 * A call in the form of macro-or-builtin() will result in:
 *                      argv[0] = nullstr
 *                      argv[1] = macro-or-builtin
 *                      argv[2] = nullstr
 *
 * argc is 3 for macro-or-builtin() and 2 for macro-or-builtin
 */

dumpdef

ソースをつらつら眺めていたら、楽しい組み込みマクロを発見。現在どんなマクロが登録されてるか確認出来るやつ。

vbox$ m4 def.m4 -
dumpdef()
  :
`esyscmd'       `esyscmd'
`traceon'       `traceon'
`swap'  `$2 and $1'
`spaste'        `spaste'
`shift' `shift'

上で出て来たファイルを読み込んでから、会話出来るように起動。そして、現在登録されてるのを確認。左右の名前が同一なのは、組み込みマクロ。後から登録したやつは、左側に名前、右側にその内容が示されている。

dumpdef(`swap')
`swap'  `$2 and $1'

特定のマクロだけを表示したい場合、マクロ名で実行されないようにクォートしてあげる。 もう、lispそのものだな。

struct macro_definition *
lookup_macro_definition(const char *name)
{
        ndptr p;

        p = ohash_find(&macros, ohash_qlookup(&macros, name));
        if (p)
                return p->d;
        else
                return NULL;
}

static void
dodump(const char *argv[], int argc)
{
        int n;
        struct macro_definition *p;

        if (argc > 2) {
                for (n = 2; n < argc; n++)
                        if ((p = lookup_macro_definition(argv[n])) != NULL)
                                dump_one_def(argv[n], p);
        } else
                macro_for_all(dump_one_def);
}

定義の実体は、OpenBSD特製のハッシュ関数でハンドリングされてる(そのおかげで、ポータビリティは無よとmanに明記してる)。大体Cの標準になってないからね。リナだとglibcで特製ハッシュが提供されてる。hash-table.c (for glibc)

erb

冒頭でrdocなんてのに目覚めて、erbの資料を作っちゃった。折角なんで見ている。

A very simple example is this:

require 'erb'

x = 42
template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF
puts template.result(binding)

Prints: The value of x is: 42

More complex examples are given below.

これ冒頭のイントロ部分。これって、m4の焼直しじゃん。templateで定義してるのがm4に喰わせるコード。<%= x %> は、組み込みのdefineだな。

そうすると、ユーザーが新規に定義出来る変換方法も提供されてるはずだな。いや、対象がHTMLって狭い範囲なんで、新発明する必要なないかな。


This year's Index

Home