hkoba blog

プログラマーです。プログラミング言語ミーハーです。ツッコミ歓迎です。よろしくどうぞ(能代口調)

zsh 上で .pm のメソッド名を補完する話

この記事は Perl Advent Calendar 2020 の 12/3 の記事です。

想定読者は、自分で Perl のモジュールを書く機会が有り、かつそのモジュールのメソッドをコマンド行からさっと試したい! と思ったことの有る人です。


Perlモジュール(.pm ファイル)を実行可能なスクリプトにする技法(モジュールに unless caller で直接実行時の処理を書いておく)は 90年代から存在し、Modulino (モジュリーノ)という名前でも知られています。 更にオブジェクト指向のクラスとして書かれた Modulino にメソッドディスパッチの機能を持たせて OO な Modulino とする手法は、多くのメソッドをコマンド行から即座に試せるため、一層便利です。 私も何度か記事勉強会で発表してきました。もちろん私が仕事で新たに書く Perl モジュールは原則として全て OO Modulino です。

ただ、そんな私でも、半年前に書いたモジュールを久しぶりに使う時などは、そのモジュールがどんなメソッドを実装しているかをもう忘れているのが普通です。そのため、例えコマンド行からメソッドを呼び出せる仕組みが有ったとしても、 結局 ソースを一通り眺めて適切なメソッドを探すという無駄な時間 を要していました。

ここでもし、シェルのコマンド行の補完機能で、メソッドやオプションの名前(と、その説明文)の一覧を見ることが出来たら、(その後でソースを確認する必要は残るとしても)適切なメソッドを探す時間を大分節約できるのではないか…? そう考えて実装したのが、 zsh の completer、 _perl_oo_modulino です。(CPAN に公開済み)

デモ動画を用意しましたので、良ければご覧ください(無音、約2.5分)

デモコードのリポジトリこちらです。 デモ用に、業務コードからメソッドやオプションとその解説文だけを切り出したものです。解説文を定義している部分を以下に抜粋します>

付録

簡単に試したい

デモコードに Dockerfile を用意したのでお試し下さい。run すると zsh が起動します。 上記の demo3 が workdir /data/ の下にあります。

hkoba の Base クラスって何?

こちらです。(長い名前なので、私は普段 CLI_JSON と呼んでいます)>

MOP4Import::Base::CLI_JSON - metacpan.org

この CLI_JSON を継承したクラスじゃないと動かないの?

必ずしも継承する必要はない(はず)です。

  • CLI のサブコマンドをメソッドへとマップする、 OO な Modulino になっていれば、意味の有る補完が出来るはずです。

    • メソッドの解説は code attribute から取得しています。ですので、 :Doc() を定義・取得出来るよう、 MODIFY_CODE_ATTRIBUTES と FETCH_CODE_ATTRIBUTES を実装する必要があります。
      • とはいえ現状では取得側は my ($atts) = grep {ref $_ eq 'HASH'} attributes::get(\&method); $atts->{Doc} みたいなコードです。attributes の使い方を間違っている可能性もあります。ツッコミ希望です。
  • CLI のオプションを %FIELDS で定義したフィールド(メンバー変数)へとマップする仕組みなら、オプション名の補完も効くはずです。

    • オプションの解説は $FIELDS{フィールド名}->{doc} から取得しています。

zsh 側はどういう仕組み?

zsh には コマンド名のパターンに対する補完関数を定義する機能 compdef -P があります。これを使えば拡張子 *.pm 全てに対する補完を定義できます。

bash に同等の機能が有るかどうかは分かりません。有れば実装できる可能性は有ります。


以上です

読んで下さりありがとうございました。明日の記事は utgwkk さんです。

フロー vs ストックは誤った二分法なのか否かについて

この記事は、元記事に対する私の呟き

に対して西尾さんから頂いたこのお返事↓に対する返信です。


目次:

どこに、どんな違和感を感じたか

まず最初に、私の主張を明確にするため、私が西尾さんの主張の骨子だと解釈した部分を元記事から抜粋させて頂きます。

  • かつて[フロー]と[ストック]の境目が明確だった時代もあった
    • チャットがIRCだった時代のイメージ
  • そんな時代は過去の話だ
    • チャットサービスであるSlackは、チャット機能を無償提供する
    • 「チャットログのストックへのアクセス権」で課金するモデルだ
  • 今の時代、ほとんどのサービスが以下の機能を持つ
    • 情報をストックする

これに対して私が感じた違和感・疑問点は、

  • (私も職場で 2015 から有償版の Slack を使ってきたし検索も沢山使うけど) ログの検索があるから大丈夫と思ったことはない、必要な情報を得る大変さは今も変わっていないではないか。
  • そもそも Slack の(会話が蓄積されているだけの)ログは、私の考えるストックと呼べる代物だろうか?

でした。

「ストック」の定義の差異

西尾さんは記事の末尾にこう書いています。

  • この文章は「情報が流れさるのがフロー、情報が蓄積されるのがストック」と考えて書いた

つまり「ストック=情報の蓄積」という定義でしょう。

ここで辞書でストックを引いてみると 在庫, 貯めておくこと などの言葉が出てくるので、 それでも良いのかもしれません。情報処理システムの議論においては、その定義を用いる人も多いのかもしれません。

dictionary.goo.ne.jp

ですが私は、世間で「ストック, フロー」という語のペアが使われる時は、 (計算機が発明される以前から使われてきた)会計や経営、経済学における定義・概念が輸入されて使われるケースもあるのではないか?と思うのです。 (どちらの使い方が多いのか、分野によって使い方の差が有るのかは調べていません)

私は会計は独学でしか学んでいないのですが、岡部先生の複式簿記(リンク先 PDF) の「第2章 財務諸表の作成の実際」の冒頭には以下のように書かれています。

簿記をつける最大の目的は、財産移動を日々記録して、一定の会計期間の財産移動とその結果と しての財産の現状を掴むことである。財産の蓄積状況をストック (stock) と呼び、財産の変動状況 をフロー (flow) と呼ぶ。一般には、フローが起るたびにストックが変化することになるが、その 都度一々計算するのは大変であるので、会計計算には会計年度とか会計期間とかいった一年や半年 や四半期の会計の締切の周期を設け、その終了時にその直前の会計期間のフローの状況を集計し、 財産のストックを計算する。

```

       期首ストック + 当期フロー = 期末ストック

```

(引用内のコードブロックに生でバッククォートが出ていますが、これよりマシな表現が出来ず…ご了承下さい)

会計における定義を私なりに書き下します:

  • ストックとは(科目、残高)の組の集合
  • フローとは(日付、増えた科目、金額、減った科目)の組の順序列
    • フローを記録したものをジャーナルと呼びます。(明らかに、これはログです)

改めて辞書のストックの定義に 在庫 とあったことを思い出して下さい。 例えば他人から「在庫を教えて?」と聞かれて、「今週頭に10個あって、月曜に3個出荷、火曜に2個入荷…」 と返事をすることは非効率です。その意味でもストックという語の訳に当てられる在庫とは、 その時点での総数のことと考えたほうが、より適切ではないかと私は考えます。

会計の定義は情報システムの改善の議論には関係あるか?無いか?

会計の話は計算機による情報システムには関係ない、と思うかもしれませんが…会計におけるストックの定義が(ログではなく)残高であることには 情報システムにおいても重要な意味があると、私は考えます。なぜなら 一般化すれば、ストックとは長大なログに何らかの要約(サマリー)作用を適用して情報を圧縮したもの と言えそうだからです。 つまりそれは、人間が生ログから情報を得ようとすることに掛かるコストを節約するために生まれた知恵なのではないでしょうか?。 そのログが金勘定なのか、日々の様々な対話なのかは捨象して良いことですが、フローのログを人が直接触るより要約を作って置いたほうが良いケースだけは必ず有るだろうと思います。

今来た三行 は何にでもある…そういう話かと思います。

結論は?

  • 「フロー vs ストック」という用語・二分法は計算機システムよりも以前に、会計(複式簿記)の世界で定義され広く用いられてきた。
  • ログが蓄積されているだけでストックと呼ぶ用法は、従来の用法とは一致しない。
  • 情報システムの議論においても、会計における用語・概念整理を用いたほうが、実りが有るのではないか?

こんなお返事で如何でしょうか?

私がシェルスクリプトをZshで書く理由

まず最初に、4つのファイル foo, bar, *, xxx yyy があるディレクトリがあるとします。(試したい場合は新規のディレクトリで下記のコマンドを実行してください。)

touch foo bar \* 'xxx yyy'

さてここで、ループを使って各ファイルを一つ一つ、コマンドに食わせたいとします。 そのために、こんなループを書いたとしましょう>

for f in *; do
  ls -l $f
done

それがこんな動作になるシェルは 疲れる から、です。 f:id:hkoba501:20200603120813p:plain

zsh であれば、デフォルトの設定( no_sh_word_split かつ no_glob_subst )で使っていれば、期待通りに4回だけ、一つずつファイルを渡しながらコマンドを実行してくれます。

f:id:hkoba501:20200603123244p:plain

(なお実行中の zsh をデフォルト設定にリセットするには emulate -L zsh コマンドが使えます)

他にも理由はありますが、これだけでも十分な理由かなと。

読んで下さり、ありがとうございました。

シングルサーバーの管理自動化に、Make に代わるものが欲しい

シングルサーバーの管理自動化に ansible や chef は良い道具か?

ansible によるサーバー運用管理の自動化が流行っているようですね。管理対象サーバーが沢山有り、なおかつ既存のレシピ(ansible だと module?)が自分の業務に活用できるなら、強力なのでしょう。

ですが世の中には、サーバー一台でも全ての業務を余裕で処理できてしまい、かつ自社の独自アプリ群の運用が主なテーマであるようなお仕事もありまして…

そういうお仕事で、 ansible (に限らず、流行の運用自動化ツール… chef? puppet?) は果たして便利なんだろうか?という疑問が有ったりします。例えば:

  • ansible は yaml で簡潔に書けるように頑張ってるけど、ツイッターを見ていると、変数展開?の挙動に関する苦情をチラホラ見かけるような? (何となく、グリーンスパンの第10法則が思い出されます…)
    • chef の設定ファイルは ruby だから、そういうピジン言語の悩みは無さそうですが…
  • chef も、設定ファイルが ruby だとファイル名を沢山書く時に疲れませんか? doublequote の嵐で…
    • サーバーの設定はファイル名やらホスト名やら…大量の文字列を扱うので、それを汎用言語の構文に収まるように変換しながら書くことは、単純に労力が大きい、という面があるかと思います。
  • そしてしばしば、最後は shell (bash) スクリプトの呼び出しになってませんか?

みたいなことを思うのです。

生の shell(bash) スクリプトも色々辛い

かと言って生の shellスクリプトだと(確かにファイル名とかは雑に書けて楽なのですが)、 そもそも shell(bash)スクリプトの弱点として

  • 変数展開一つとっても罠が多い(初心者殺し)
  • エラー処理を堅く書くのが大変(難度が高い)
  • Unit Test を書くのも大変

という問題があります。

その上、管理自動化用の機能を全部自分で書く必要があるでしょう。例えば:

  1. べき等性の実現方法
  2. dry-run の仕組み
  3. タスクの依存関係の処理

このうち 1, 2 は shellスクリプトの書き方に慣れればある程度は対応できます。 ただ、shell(bash)スクリプトでは複雑なデータ構造のサポートが足りないため、タスクの依存関係の処理までは 手が回らないことが多いのではないでしょうか?

Makefile はファイルの mtime ベースでしか依存関係を扱えない

この 3つを一挙に解決する道具として Makefile を使う手もあります。 特に最終的な目標がファイルの作成である場合は、とても有効です。

しかし、Makefile はファイルのタイムスタンプのみに基づいて依存関係を処理します。 ですから例えば /etc/ のあるファイルの中に〇〇が記述されていなければ記述を足す、みたいな処理を書くことは困難です。 つまり、サーバーの設定ファイルの自動書き換えには向かない面があります。

bash が嫌なら Tcl はどうか

ところで皆さんお忘れですが Tcl という言語が存在します。 Tcl は文字列の扱いに関して shell よりも更に緩くかつ構文を最小限に絞っています。 にも関わらず例外処理や名前空間があり、ある程度複雑なものも書ける、本格的なプログラミング言語です。

ということは、 Tcl に Make に似た依存関係探索ライブラリーが有ったら、サーバー管理の自動化に 使えるのではないかしら?

という思いつきで実験を重ねてきたコードがこちらです。その名の通り wip ですが、やりたいことは伝わるのではと> GitHub - hkoba/wip-TclTaskRunner0: WIP: Small task runner library in Tcl. Makefile alternative.

この現在の実験版では、例えば、 webmaster というユーザーと devel というグループのべき等な追加処理を下記のように書けます。 check が検査のコードで、check が false を返した時のみ、 action ブロックが実行されます。 ** は dry-run のマーカーです。タスク定義ファイルの中には普通の Tcl の手続きや変数を定義して使うことも可能です:

#!/usr/bin/env TclTaskRunner.tcl

import {file-has} from utils.tcl

default target all dependsTasks {
    webmaster
    devel
}

target webmaster check {
    check-user $target
} action {
    ** exec useradd -s /sbin/nologin $target
}

target devel check {
    check-group $target
} action {
    ** exec groupadd $target
}

proc check-user {user} {
    # id コマンドの呼び出しても良いですが、ファイルの中身の確認の例ということで…
    file-has ^${user}: /etc/passwd
}

proc check-group {group} {
    file-has ^${group}: /etc/group
}

こういう構想にご興味ございましたら是非お話しましょう〜

いまは例えばこういう所を悩んでいます:

  • タスクを宣言するコマンドの名前は target よりも task の方が良い? (タスクランナーって名乗っているのだから…けど依存関係は target って言ったほうが自然な気がする)
  • 再帰 Make 的なものの実例が欲しい気がする。
  • リモートなタスクへの依存関係も記述したい。 (リモート実行は sshcomm の出番)
  • make -j みたいな並列実行もしたいのでは? thread か?
  • Make の重要な機能である、変数をコマンド行からオーバライドする機能が欲しい気がするが、どんな実例を書こうか

ここまで読んで下さり、有難うございました。

Modulino + OOPの提案 - CLIからオブジェクトと遊ぼう

この記事は *.pm による Perl のクラス定義を CLI から直接的に試せるようにする開発技法の提案・解説です。

はじめに

Perl のモジュールを CLI コマンドとしても使えるようにする手法は Modulino (モジュリーノ=コマンドとしても実行可能なモジュール) と呼ばれます。この記事では私が長年探求してきた Modulino を OOP と組合せて Perl 開発に役立てる方法の、最も基礎となる部分について解説します。 (OO Modulino 技法とでも言いましょうか)

要約すると

  • クラス定義を Modulino にし、CLIから起動できるようにしておく
  • CLI へのサブコマンドをメソッドに対応付けて dispatch する
  • CLI への posix 風オプション(--name=value) を new の引数にする

というアプローチが良いという考えです。

復習: Modulino(モジュリーノ) とは

Modulino とは、 Perl の正当なモジュールファイルであり、 なおかつ端末からコマンドとして実行することも出来るような .pm ファイルのことです。

例えば以下のような(Modulino ではない)普通のクラス定義モジュールファイル A.pm を考えます。 (ここでは説明を簡略化するため use strict; use warnings; を省略します。)

package A;
sub new  { my $class = shift; bless +{@_}, $class }
sub hello { my $self = shift; join " ", "Hello", $self->{name} }
1;

これをコマンド行から試すには、こう書く必要があります。

% perl -I. -MA -le 'print A->new(name => "world")->hello'
Hello world
%

そこそこ長いですね。初心者に打たせるのは厳しい感じですし、 Perl に慣れた人でも少しダルく感じるのではないでしょうか。

これを Modulino に書き換えてみましょう。 unless (caller) という見慣れない構文が鍵となります。

#!/usr/bin/env perl
package A;
unless (caller) {
  # コマンド行から呼ばれた時にしたい処理をここに書く。例えば…
  print A->new(name => shift)->hello, "\n";
}
sub new  { my $class = shift; bless +{@_}, $class }
sub hello { my $self = shift; join " ", "Hello", $self->{name} }
1;

これなら、コマンド行から即座に実行できます。 ファイル名に shell の補完も効きますし。

% chmod +x A.pm
% ./A.pm world
Hello world

また、perlデバッガーを呼び出したい時も頭に perl -d を付けるだけです。余計なコーディングは不要です。

% perl -d ./A.pm world

OOP なモジュールの unless caller には何を書くと便利か

サブコマンドをメソッドに

先程の A.pm は unless caller ブロックで A->hello を直接呼び出していました。

しかし大抵のクラスのオブジェクトは複数のメソッドを持つでしょう。 他のメソッドを試したい時は、やはり CLI に長いコマンドを書く必要が生じます。

% perl -I. -MA -le 'print A->new(name => "world")->goodnight'

なら git のようにサブコマンドを取ることにして、 それをメソッド名として解釈してはどうでしょう?つまり unless caller ブロックに第一引数に基づく dispatch 処理を組み込むのです。 (引数不足のエラー処理は省略します)

unless (caller) {
  my $self = A->new(name => "world");
  my $cmd = shift;  # CLI の第一引数をメソッド名に使う
  print $self->$cmd(@ARGV), "\n";
}

これで任意のメソッド名を呼び出すことが出来ます

% ./A.pm goodnight
Good night world

posix long オプションを new の引数に

任意のメソッドを呼び出すことは出来るようになりました。…けど、まだ不満が有りますね? そう、new の引数が name => "world" に固定なのです。これもコマンド行から変更可能にしたい。

これも git のように、サブコマンドの前の --name=value スタイルの引数列を全て new に渡すようにしては どうでしょう?

追記: --name value のような書き方が使えず不満を感じる人もいるかもしれません。その場合は こちら の記事も読んでみて下さい

unless (caller) {
  # CLI から渡された --name=value 形式の引数を全て new に渡す
  my $self = A->new(name => 'world', _parse_posix_opts(\@ARGV));
  my $cmd = shift;
  print $self->$cmd(@ARGV), "\n";
}

sub _parse_posix_opts {
  my ($list) = @_;
  my @opts;
  while (@$list and $list->[0] =~ /^--(?:(\w+)(?:=(.*))?)?\z/s) {
    shift @$list;
    push @opts, $1, $2 // 1;
  }
  @opts;
}

これで new の引数も CLI から制御できるようになりました。

% ./A.pm hello
Hello world
% ./A.pm --name=Perl hello
Hello Perl

まとめ

Perl のクラス定義に unless (caller) ブロックを加えて、 オブジェクトの挙動をコマンド行から簡単に試せるようにする技法の基礎について 解説しました。

OO Modulino には他にも

  • Modulino の @INC 調整に FindBin を使った場合に起きる問題とその対処法
  • オブジェクトのメンバー変数を use fields で宣言しておき、メソッドの引数宣言を my TYPE $self のようにクラス名も書くことで、メンバー変数の typoコンパイル時に検出する
    • 加えて new 時に fields::new($class) を使うことで、不正なオプションを渡されても実行時エラーにする
  • メソッドの戻り値は Data::DumperJSON を使ってシリアライズして出力する
    • CLI 専用に作ったメソッドと一般のメソッドを区別できるよう、 cmd_ などのメソッド名接頭辞を予め決めておく。
  • CLI の引数に {..} [..] が現れた場合は JSON として扱う
  • ./A.pm <TAB> でメソッド名やオプション名を補完する zsh completer

などの発展的な話題があります。それらを全て盛り込んだベースクラス MOP4Import::Base::CLI_JSON の話も、機会が有れば書いてみたいと思います。

また、新人教育に Modulino を取り入れる方法のメリットについてはこちらの記事も参考にどうぞ>
-nle と .pm から始める Perl 入門とかどうかしら?(後編) - hkoba blog

ここまで読んで下さりありがとうございました!

libperl-rs の話

f:id:hkoba501:20200329224058p:plain
Rustだからカニを貼ってみた

昨年2019の夏頃から libperl-rs というライブラリを作っています(現状では仕事とは無関係の、純然たる自宅研究です。進みも間欠的です)。これは Perl5 のランタイムライブラリーである libperl を Rust から呼び出すためのラッパーライブラリーです。

github.com

libperl-rs については、これまでは YAPC Japan 2019 名古屋で発表した以外は、 これといった情報開示をしてきませんでした。

hkoba.github.io

別に秘密というわけではなく、 単に blog を書くことを面倒臭がっていただけで、 github 上には 少しずつ進展を出してはいました。ですが最近、私が前もって現状を整理して公開していれば、誰かさんを困らせなくて済んだのかもなー…と思うこともあり…

ちょっと現状を整理して開示しておこうと思います。

libperl-rs の開発目標、libperl-rs で何を出来るようにしたいか

私が libperl-rs で一番やりたいことは、 Perl プログラムの AST (OP Tree) の解析です。 特に、動的に生成されるスクリプトの AST を解析し、その実行前にエラーを検出したり、 Language Server やデバッガーを提供したり…ということが出来るような道を開きたいと考えています。 (そこまでたどり着けるとは言い切れませんが…)

libperl-rs の現状

現在の libperl-rs は3つの crate から構成されています。

  1. libperl-config - env perl からビルド情報を取り出すためのライブラリーです。 2., 3. のビルドに用います。
  2. libperl-sys - env perl 付属の libperl に対する Rust binding を生成する crate です。bindgen を用いて C のヘッダーから Rust のコードを自動生成します。
  3. libperl-rs - libperl-sys の機能を Rust らしく扱えるようにするためのラッパーライブラリーです。
%  tree -L 2
.
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── build.rs
├── examples
│   ├── 000_perl_parse.rs
│   ├── 001_perl_parse_args.rs
│   ├── 100_scan_ops.rs
│   ├── 101_scan_ops_debug.rs
│   ├── 102_padname_type.rs
│   ├── 103_scan_op_tree.rs
│   ├── 104_enum_op_tree.rs
│   ├── 105_scan_stash.rs
│   ├── 106_scan_allpackage.rs
│   ├── 107_scan_subs_in_a_file.rs
│   ├── 108_scan_subs_in_a_file2.rs
│   ├── 109_scan_subs_intro1.rs
│   ├── 110_call_method.rs
│   └── eg
├── libperl-config
│   ├── Cargo.lock
│   ├── Cargo.toml
│   └── src
├── libperl-sys
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── LICENSE
│   ├── build.rs
│   ├── examples
│   ├── script
│   ├── src
│   └── wrapper.h
├── release.toml
├── runtest-docker.zsh
├── src
│   ├── lib.rs
│   └── perl.rs
└── target
    ├── debug
    └── rls

現状では 1., 2. については API はほぼ安定しているのではないかと思います。(改良の余地は有るかもしれません)

問題は 3. の libperl-rs です。 2. のbindgen によって生成された libperl-sys は Perl の C API からマクロを取り去った、裸の API です。しかも threaded 版と非スレッド版の2系統が存在します。それを Rust の層で抽象化し直す必要があります。 私がまだ Rust の修行中であることもあり、一発で正解にたどり着く自信はありません。 とても API が固まったとは言えない状況です。

そのため、現在は主として examples ディレクトリーの下に 実験的なコードを書き、その中で今後 libperl-rs の本体に組み入れると良さそうなコードを eg サブディレクトリーに作り貯めている所です。 皆さんのツッコミをお待ちしております。

今の libperl-rs で出来ること

今の段階の libperl-rs で出来ることは、どれも B モジュール(とても頑張れば)出来ることばかりだと思います。

とはいえ、OP Tree に対してパターンマッチが出来るようになりつつあることは、 少なからぬメリットなのではないでしょうか。以下は Perl のサブルーチンの冒頭から my (...) = @_ に相当する AST を抜き出すための、パターンマッチのコードです。 (少し冗長ですが)

        let ast = op_extractor.extract(cv, CvROOT(cv));
        
        match ast {
            Op::UNOP(opcode::OP_LEAVESUB, _
                     , Op::LISTOP(opcode::OP_LINESEQ, _
                                  , Op::COP(opcode::OP_NEXTSTATE, body), _), _) => {
                println!("preamble!");
                match body {
                    Op::BINOP(opcode::OP_AASSIGN, _
                              , Op::UNOP(opcode::OP_NULL, _
                                         , Op::OP(opcode::OP_PADRANGE, _, _
                                                  , Op::UNOP(opcode::OP_RV2AV, _
                                                             , Op::PADOP(opcode::OP_GV, _
                                                                         , Sv::GLOB { name: ref nm, .. })
                                                             , _))
                                         , lvalue)
                              , _) if nm == "_" => {
                        println!("first array assignment from @_, lvalue = {:?}"
                                 , match_param_list(lvalue));
                        
                    }
                    _ => {
                        println!("first statement is not an array assignment");
                    }
                }
            }
            _ => {
                println!("doesn't match")
            }
        }

他にも (perl_parse の作る) 静的な OP Tree を調べるコードだけではなく、 perl_parse 後に任意のクラスの任意のメソッドを Rust から呼び出してみるサンプルも、(まだまだベタな書き方ですし不足もありますが)動き始めています(抜粋)。

#[cfg(all(perl_useithreads,perlapi_ver26))]
fn call_list_method(perl: &mut Perl, class_name: String, method_name: String, args: Vec<String>) -> Result<Vec<Sv>,String>
{

    let mut my_perl = perl.my_perl();

    // dSP
    let mut sp = my_perl.Istack_sp;

    // ENTER
    unsafe_perl_api!{Perl_push_scope(perl.my_perl)};

    // SAVETMPS
    unsafe_perl_api!{Perl_savetmps(perl.my_perl)};

    // PUSHMARK(SP)
    perl.pushmark(sp);
    
    // (... argument pushing ...)
    // EXTEND(SP, 1+method_args.len())
    sp = unsafe_perl_api!{Perl_stack_grow(perl.my_perl, sp, sp, (1 + args.len()).try_into().unwrap())};
    
    for s in [&[class_name], args.as_slice()].concat() {
        sp_push!(sp, perl.str2svpv_mortal(s.as_str()));
    }

    // PUTBACK
    my_perl.Istack_sp = sp;

    // call_method
    let cnt = unsafe_perl_api!{Perl_call_method(perl.my_perl, method_name.as_ptr() as *const i8, (G_METHOD_NAMED | G_ARRAY) as i32)};
    
    // SPAGAIN
    // sp = my_perl.Istack_sp;
    // (PUTBACK)

    let res = stack_extract(&perl, cnt);

    // FREETMPS
    perl.free_tmps();
    // LEAVE
    unsafe_perl_api!{Perl_pop_scope(perl.my_perl)};
    
    Ok(res)
}

libperl-rs の試し方

現段階では github の master を試して頂くのが良いと思います。(crates.io にあるものは、YAPC Japan 2019 名古屋の 時のバージョンです。それ以後の改良部分を crates.io に送るのは、もう少し API が固まってからが良いのかなと…)

私自身の開発環境は Fedora 30 + System Perl (perl5.28) + System Rust (1.42) ですが、 travis-ci 上では Perl 公式 Docker イメージを用いて Perl 5.30〜5.22 での動作を確認してあります。 (発展的な例は threaded build な Perl5.26以上限定です) (libperl-sys までなら、もっと古い Perl でもビルド出来そうな気がします)

ですので、Debian, Ubuntu の上であれば以下の手順で動かしてみることは可能でしょう>

準備
    apt update &&
    apt install -y llvm-dev libclang-dev clang &&
    curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh &&
    sh rustup.sh -y &&
    source $HOME/.cargo/env &&
    rustup component add rustfmt

git clone https://github.com/hkoba/libperl-rs && cd libperl-rs
実行

examples/109_scan_subs_intro1.rs を実行してみる(渡したスクリプトの namespace から my (...) = @_ 形式の引数宣言だけを抜き出すサンプル)

% Z-chtholly(pts/2)% cargo run --example 109_scan_subs_intro1 -- -le '
package Int; 
sub foo { (my Int $x, my Int $y) = @_; $x + $y }
'
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/examples/109_scan_subs_intro1 -le '
package Int; 
sub foo { (my Int $x, my Int $y) = @_; $x + $y }
'`
$0 = "-e"
sub "foo"
preamble!
first array assignment from @_, lvalue = [PadNameType { name: Some("$x"), typ: Some("Int") }, PadNameType { name: Some("$y"), typ: Some("Int") }]

最後に

ここまで読んで下さり有難うございました。ご意見お待ちしております!

2022-03-30 追記: gist-it が死んでいてソースコード参照が動いていなかった箇所をコード埋め込みに変えました。

謹賀新年/2020目標

あけまして、おめでとうございます

旧年中に仲良くして下さった皆さん、有難うございました。 今年もどうぞよろしくお願いします。

2019 の良かったこと

  • 遅まきながら、少しずつ GCP の業務投入を始められたこと。
  • libperl-rs の開発経験を通して、Rust に活路を見いだせそうな予感を得たこと(まだまだ勉強中ですが…)
  • マニュアルを書くためのツール、sphinx も試したけど mdbook の方が設定無しですぐにそこそこ使えて自分向きだと分かったこと。
  • (以前から構想していたことだけど、実際に)職場の Perl の新人教育のプロセスを -nle.pm から始めるよう試した結果、それなりの効果を確認できたこと。少なくとも .pm から教えることは現代的には全く問題が無い。ボイラープレートは File::AddIncM4I の CLI_JSON にまとめる方向で引き続き。

  • 艦これアーケードの秋イベント、なんとか甲クリアを継続できたこと

2019 の反省点

  • 仕事
    • 職場の新人教育、途中から私が多忙で放置になってしまったこと。もっとフィードバックを返したかった。
    • タスク管理が迷走したこと。 phabricator ←→ redmine どっちも合わない。ぐぬぬ
    • GCP での環境構築、スケジュールに追われたせいでシステム化(自動化)が後回しになっていること。
    • YATT の LSP、emacs の lsp-mode との組合せが今一思い通りにならず、その後の機能強化も停滞。後半に eglot に切り替えて少しマシになったけど。
      • かといって VS Code もまだ手に馴染むほどではなく… Emacs 使っている時より全体的に半分以下の速度しか出せない感覚…何が原因なのか…画面の切り替え?カーソル移動?マウスを触っている時間?
  • 生活
    • ジム中断(肘の痛みが原因)
    • 部屋の片付け・もの減らしが停滞している。
    • 婚活進展ゼロ。50歳なので街コンも行けないのだ。
      • とら婚さんにも相談してみたけど、結婚感というやつを言語化するのが大事だそうな。

2020 の目標

  • もっとドッグフード食っていきたい。
  • GCP 周りの自動化をもっと。ただし自分のフィールドを見失わない(世間の流行りのツールが自分の領域に適するかどうかは別問題)。Ansible とはまた違うアプローチを探りたい。
  • libperl-rs を引き続き強化して、 Perl コードの静的解析のための基盤へと育てる
    • Rust をもっと自分の道具にする。YATT や GUI ツール、自作言語のためにも使えないか。
  • 今年こそタスク管理ツールの移行を… gitlab か gogs か…
  • ジム再開?(肘の痛みは、持病と思うしか無いかも…)