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(¯os, ohash_qlookup(¯os, 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って狭い範囲なんで、新発明する必要なないかな。