yacc

DBM

突然ですが、DBMとかけて何と解く?

そりゃ、出身によるわな。ソフト屋さんなら、永続版のhashであるndbmとかgdbmとかを連想するだろう。何せhashですから。。。

電子屋さん、しかも貧乏人は、ダブル・バランスド・ミクサーを思い出すだろう。ミクサーってのは、早い話、かけ算器だ。それが二重にバランスしてる??? 水晶フィルターを購入する亊が出来無い、貧乏中学生が夢みたSSBのジェネレータ。の心臓部だ。

信号Sと搬送波Cのかけ算と和(もしくは差)で出来上がっている。

[sakae@fb /tmp]$ maxima
(%i1) trigreduce(sin(S)*cos(C)) + trigreduce(cos(S)*sin(C));
(%o1)                             sin(S + C)
(%i2) trigreduce(sin(S)*cos(C)) - trigreduce(cos(S)*sin(C));
(%o2)                             sin(S - C)
(%i3) trigreduce(sin(S)*cos(C));
                            sin(S + C)   sin(S - C)
(%o3)                       ---------- + ----------
                                2            2

信号も搬送波も、90度位相がずれた信号が必要なのが難点。

普通に三角関数をかけ算すると、積和公式(上の%3)により、キャリアーに対して上側波帯と下側波帯が発生する。位相差をもった波をミックスして、加算もしくは減算すると、側波帯のどちらかが得られる。これが、数学的な原理。

キャリアー信号を90度ずらすのは簡単だけど、信号になるオーディオを90度ずらすのは至難の技。抵抗とコンデンサーで移相器を作ったものだ。今だと、ディジタル的に出来ちゃうのかな。

z変換とかね。これって、ラプラス変換の離散的な表現らしい。いつか首を突っ込んでみたいものだ。

数理モデルと微分方程式

前回のSDRのYoutubeで、未来を予測するなんて言う刺激的な文言が飛びかった。そうか、微分方程式を立てられば、天下無敵、競馬で億万長者間違いなしなんだな。

微分方程式をたてるって、数学の分野をちょっぴり外れる話題なんだな。ぐぐってみたら数理モデルって分野になるっぽい。

漸化式を使っていろいろな現象を数学にしてみよう!

数理モデルと微分方程式

みんなにわかってほしい数理モデルと意思決定

yacc

rubyの肝は、その仕様をプログラムの落とし込む部分にあるらしい。

LoveRuby.Net こちらのRHGに詳しい説明がある。

Cとyacc/lex いきなりのrubyがキツいという場合は、やさしいのから手を出すのが良かろう。

bc

FreeBSDのbcがどうなってるか、視察してみる。記録が残る電卓ですから、後で検証が容易ですよ。そんじょそこらの(GUI)電卓とは違うんです。

まず軽く、コンパイルしてみる。

yacc -d  -o bc.c bc.y
yacc: 1 shift/reduce conflict, 16 reduce/reduce conflicts.
lex   -oscan.c scan.l
 :
cc -O2 -pipe -I. -I/tmp/bc -g -std=gnu99 -Wno-format-zero-length -nobuiltininc -idirafter /usr/lib/clang/13.0.0/include -fstack-protector-strong -Qunused-arguments    -o bc.full bc.o scan.o tty.o  -ledit
objcopy --only-keep-debug bc.full bc.debug
objcopy --strip-debug --add-gnu-debuglink=bc.debug  bc.full bc

yacc/lexがちゃんと使われていた。しかも編集機能付きです。

軽く bc -hして、試用説明書をみておく。

-l  --mathlib

    Use predefined math routines:

        s(expr)  =  sine of expr in radians
        c(expr)  =  cosine of expr in radians
        a(expr)  =  arctangent of expr, returning radians
        l(expr)  =  natural log of expr
        e(expr)  =  raises e to the power of expr
        j(n, x)  =  Bessel function of integer order n of x

着本的には、関数電卓。関数機能をONして使ってみる。

[sakae@fb ~]$ bc  -l
e(1)
2.71828182845904523536
s(1.0)
.84147098480789650665
scale = 50
4 * a(1)
3.14159265358979323846264338327950288419716939937508
pi(50)
3.14159265358979323846264338327950288419716939937510
print("Hello BC\n")
Hello BC
define myadd(a,b) { a + b }
myadd(10,3)
13
0
define mymul(a,b) { return (a * b) }
mymul(3,4)
12

プログラマブル電卓なのね。じゃ、その核心をみる。

[sakae@fb /tmp/bc]$ yacc -v bc.y
yacc: 1 shift/reduce conflict, 16 reduce/reduce conflicts.
[sakae@fb /tmp/bc]$ grep  conflicts y.output
State 102 contains 4 reduce/reduce conflicts.
State 161 contains 2 reduce/reduce conflicts.
State 162 contains 2 reduce/reduce conflicts.
State 163 contains 2 reduce/reduce conflicts.
State 164 contains 2 reduce/reduce conflicts.
State 165 contains 2 reduce/reduce conflicts.
State 166 contains 2 reduce/reduce conflicts.

これぐらいになると、どうしたらいいの、が出てくるのね。

bc.libraryに、sin関数等のdefineが定義されてた。マクローリン展開ではなさそうだし、収束の早い計算方法って、どんなのが有るのだろう?

First, if a function is called on startup to turn bc(1) into a
number converter, it is possible to replace that capability with
various shell aliases.  Examples:

       alias d2o="bc -e ibase=A -e obase=8"
       alias h2b="bc -e ibase=G -e obase=2"

進数変換はdcと言うRPNカリキュレータの得意技かと思っていたら、36進数までは上のようにして自由に定義出来るのね。

ついでにHP電卓の心臓部であるdcをみたら、yacc/lexは使っていなかった。スタックが主戦場だから、無理して使う亊は無いのか。

[sakae@fb /tmp/t]$ cat mye.bc
scale = 10
define mye(x){
        auto a, b, c, i, s
        a = 1
        b = 1
        s = 1
        for(i=1; 1==1; i++){
                a = a*x
                b = b*i; print "b= "; b
                c = a/b; c
                if(c == 0) return(s)
                s = s+c
                s
        }
}
mye(1)
quit

autoは、ローカル変数の宣言なんだな。それから、どんな風に計算が進行するか、主な変数をモニターしてみた(単に変数名を列挙すると、値が表示される)。

[sakae@fb /tmp/t]$ bc mye.bc
b= 1
1.0000000000  ;; c
2.0000000000  ;; s
b= 2
.5000000000
2.5000000000
b= 6
.1666666666
2.6666666666
b= 24
.0416666666
2.7083333332
 :
b= 87178291200
0
2.7182818277

関数のマクローリン展開 を、愚直に実行しているんだね。

初等関数の計算

いつ反復計算をやめるべきか? : 収束判定基準の設定方法

like ruby

関数定義が、defineで始める亊になっている。ちょいと気にいらない。defineはschemeのものだろう。rubyに似せて、defでいけるようにしたい。どこを改造すればいい?

[sakae@fb /tmp/bc]$ grep -n define bc.y scan.l
  :
bc.y:128:#define MAX_VARIABLES  (VAR_BASE * VAR_BASE)
bc.y:400:                 LBRACE NEWLINE opt_auto_define_list
bc.y:459:opt_auto_define_list
bc.y:461:               | AUTO define_list NEWLINE
bc.y:462:               | AUTO define_list SEMICOLON
bc.y:466:define_list    : LETTER
bc.y:476:               | define_list COMMA LETTER
bc.y:481:               | define_list COMMA LETTER LBRACKET RBRACKET
scan.l:53:#define YY_DECL       int yylex(void)
scan.l:54:#define YY_NO_INPUT
scan.l:56:#define YY_INPUT(buf,retval,max) \
scan.l:132:"define"     return DEFINE;

ふむ、scan.l を変更すればいいのかな。そんなの、すぐに気付よ。lexが処理するソースに対応が定義されてるに決っているだろ。まれに規模が小さいと、parse.yに押し込んでしまう亊もあるようだけど。

実際にやってみるとこんなエラー。元に戻しても同様。

[sakae@fb /tmp/bc]$ ./bc mye.bc

Parse error: bad character 
    <stdin>:2

ええい、面倒だって亊で、rootになって本家本元を修整してインストール。でも、同様のエラーが発生。諦めるか、何か見落としが有るんだろう。まて、河岸を変えてOpenBSDでチャレンジングって手もあるな。

早速やってみたら、bcのコンパイラーの所でdcも必要って言われた。どうもdcに定義されてるソースの一部を利用してるみたいだ。bcとdcは、同じ血をわけた兄弟なんだな。なんとなく名前が簡潔なわりには似てる雰囲気を醸し出していたけど、そういう繋がりがある訳ね。

で、結果は、大成功。rubyぽくなったよ。よかった、よかった。 じゃ、FreeBSDで失敗した原因は何? コンパイル・ログを見るとobjcopyとか変な亊をやってるし。。。これにて、打ち止め。

後日、/usr/portsをアップデートしようとしたら、何時まで待っても終了しない亊に気が付いた。こういう時は、プロセスがどういう状態になってるか確認するのが鉄則。

1716  3  I+   0:00.01 make update
1779  3  I+   0:00.02 /bin/sh /usr/sbin/portsnap -p /usr/ports fetch
1945  3  I+   0:00.00 xargs /usr/libexec/phttpget ipv4.aws.portsnap.freebsd.org
1946  3  I+   0:00.00 /bin/sh /usr/sbin/portsnap -p /usr/ports fetch
1947  3  I+   0:00.00 /bin/sh /usr/sbin/portsnap -p /usr/ports fetch
1949  3  I+   0:00.04 /usr/libexec/phttpget ipv4.aws.portsnap.freebsd.org bp/dd
1950  3  I+   0:00.00 bc
1951  3  I+   0:00.00 dc -x

がーん、全く関係ないと思っていたbcと、兄弟のdcが顔を出しているよ。rootでインストールしてしまったbcがちゃんと機能してない予測がたつな。さー、困った、どうする?

bc == dc ?

普通に考えたら、修復CDなりを使って、bcを上書きだな。ISOを取ってくる亊にしよう。

リナでISOをDLしてたので、取り敢えずリナで確認。

sakae@deb:~$ sudo mount -o loop -t iso9660 /meet/FreeBSD-13.1-RELEASE-i386-disc1.iso /mnt
mount: /mnt: WARNING: source write-protected, mounted read-only.
sakae@deb:~$ ls -l /mnt/usr/bin/{bc,dc}
-r-xr-xr-x 2 root root 211672 May 12 18:06 /mnt/usr/bin/bc*
-r-xr-xr-x 2 root root 211672 May 12 18:06 /mnt/usr/bin/dc*

兄弟のbcとdcを、取り敢えず確認。何と瓜二つですよ。 これはもう、 FreeBSDにいって、コピペだな。その前に、現状の確認をしとく。

[sakae@fb /usr/bin]$ ls -l bc dc
-r-xr-xr-x  1 root  wheel   58540 Jun  4 09:14 bc*
-r-xr-xr-x  1 root  wheel  211672 May 19 07:08 dc*

壊れた(壊した)bcがインストールされてるよ。

下記のように一手間かけてマウント

mdconfig -a -f /meet/FreeBSD-13.1-RELEASE-i386-disc1.iso -u 0
mount_cd9660 /dev/md0 /mnt

解除も一手間必要。

umount /mnt
mdconfig -d -u 0

bcをcpして、無事に修復完了。なお、bcでファイルにあるスクリプトを実行するには、-f を付ける必要があるみたい。これって、dc由来なんだな。 なんか、そんな亊bc(1)には説明が無いし、兄弟関係が分っていないと面くらうぞ。

/usr/src/usr.bin/Makefile

.if ${MK_GH_BC} == "yes"
SUBDIR+=                gh-bc
.else
SUBDIR.${MK_OPENSSL}+=  bc
SUBDIR.${MK_OPENSSL}+=  dc
.endif

これを見ると、/usr/src/contrib/bcにある寄贈品が元本になってた。まさに兄弟でした。 そんな亊、しらんかったぞ。

[sakae@fb /tmp/t/bc/src]$ ls
args.c          dc.c            lang.c          opt.c           vector.c
bc.c            dc_lex.c        lex.c           parse.c         vm.c
bc_lex.c        dc_parse.c      library.c       program.c
bc_parse.c      file.c          main.c          rand.c
data.c          history.c       num.c           read.c

寄贈品はC言語のみの提供になってた。時代は変わるのね。

doas

parse.yというファイルをOpenBSDから探し出してみた。複数見付かったけど、日頃お世話になってる、doasに敬意を表して、これにターゲットを絞ってみる。doasコマンドはsudoからBUGを取り除いて整理したものだ(ちょっとその言は飛躍しすぎ?)。

機能を絞ってBUGが少くなるようにしましたって亊だ。設定は、これしかない。 doas.confの説明より引用。

The rules have the following format:
      permit|deny [options] identity [as target] [cmd command [args ...]]

options      Options are:
             nopass   The user is not required to enter a password.
             nolog    Do not log successful command execution to
                      syslogd(8).
    :
identity     The username to match.  Groups may be specified by
             prepending a colon (‘:’).  Numeric IDs are also accepted.

as target    The target user the running user is allowed to run the
             command as.  The default is all users.
example    
      permit persist setenv { PKG_CACHE PKG_PATH } aja cmd pkg_add
      permit setenv { -ENV PS1=$DOAS_PS1 SSH_AUTH_SOCK } :wheel

この設定を解析してソースに反映させるのにparse.yが使われている。 コンパイルは、下記のように進んでいく。

vbox$ make
yacc  -o parse.c parse.y
cc -O2 -pipe  -I/tmp/doas -Wall -MD -MP  -c parse.c
cc -O2 -pipe  -I/tmp/doas -Wall -MD -MP  -c doas.c
cc -O2 -pipe  -I/tmp/doas -Wall -MD -MP  -c env.c
cc   -o doas parse.o doas.o env.o 

-o で変換後のソース名を指定してるけど、普通にyacc すると、デフォのファイル y.tab.cが作成される。

vbox$ wc parse.y parse.c y.tab.c
     354    1085    7063 parse.y
     840    2763   20331 parse.c
     840    2763   20331 y.tab.c

それから、yaccに -v を付けると、ユーザーに易しい文法を出力してくれる。

vbox$ yacc -v parse.y
vbox$ cat y.output
   0  $accept : grammar $end

   1  grammar :
   2          | grammar '\n'
   3          | grammar rule '\n'
   4          | error '\n'

   5  rule : action ident target cmd

   6  action : TPERMIT options
   7         | TDENY

   8  options :
   9          | options option

  10  option : TNOPASS
  11         | TNOLOG
  12         | TPERSIST
  13         | TKEEPENV
  14         | TSETENV '{' strlist '}'
   :
state 31
        strlist : strlist . TSTRING  (16)
        args : TARGS strlist .  (23)

        TSTRING  shift 27
        '\n'  reduce 23

16 terminals, 11 nonterminals
24 grammar rules, 32 states

これで、別の角度から、間違いを検出しましょって言う、二重安全化が施されている。

doas.conf(5)

persist  After the user successfully authenticates, do not
         ask for a password again for some time.

に、こんな説明が有った。some timeって何よ? ソース嫁。

static void
authuser(char *myname, char *login_style, int persist)
{
    :
        if (persist)
                fd = open("/dev/tty", O_RDWR);
        if (fd != -1) {
                if (ioctl(fd, TIOCCHKVERAUTH) == 0)
                        goto good;
        }
     :
good:
        if (fd != -1) {
                int secs = 5 * 60;
                ioctl(fd, TIOCSETVERAUTH, &secs);
                close(fd);
        }

一度認証に成功すると、それから5分以内は、再認証(passwdの入力)無しに、認証が継続されるって読めるんだけど。元に遡ってみる。

tty(4)

TIOCSETVERAUTH int *secs
            Indicate that the current user has successfully authenticated
            to this session.  Future authentication checks may then be
            bypassed by performing a TIOCCHKVERAUTH check.  The verified
            authentication status will expire after secs seconds.  Only
            root may perform this operation.

ああ、いきなり、興味が有る所を調べちゃったけど、parse.yとの連携は、

struct rule {
        int action;
        int options;
        const char *ident;
        const char *target;
        const char *cmd;
        const char **cmdargs;
        const char **envlist;
};

rouleの個数は、parse.y にmaxrules=32って風に定義されてた。それから、doas.cに

static void
parseconfig(const char *filename, int checkperms)
{
   :
        yyparse();

yyparse()は、parse.yを変換したparse.cに、勿論定義されてる。これで臍の緒が繋った。

case 14:
#line 150 "parse.y"
{
                        yyval.options = 0;
                        yyval.envlist = yyvsp[-1].strlist;
                }
break;
case 15:
  :

こんなのがズラーと並んでいる。見るものでは無いな。

もう、yaccは古い技術だ。今なら、rust時代にやった、パーサー・コンビネータが主流になるか。次、何やろう? しばし考え中。。。。

おまけでbcの小粋な機能

OpenBSDのbc(1)に、-cなんてオプションが提供されてた。何これと思ったら、bc語をdc語に翻訳してくれるみたいだ。そもそも最初にdcが有りき。このRPN型計算器に馴染めない人向けに、中置記法のbcが出来たみたい。

だから、bcはdcのプリプロセッサーの位置付。中置記法で入力すると、それをdc用のコマンド列に変換してくれる。

ob$ bc -c
(1 + 2) * 5
 1 2+ 5*ps.
qob$ dc
 1 2+ 5*ps.
15

1と2を加算、それに5をかけ算、結果をpで印字。s. で、stackのTOPを取出しdot-regに格納。

[la1+dsa*pla10>y]sy
0sa1
lyx
1
2
6
24
120
720
5040
40320
362880
3628800

最初の10までの階乗を計算。まさに判じもの。ブラケット内が文字列としてスタックに詰れる。


This year's Index

Home