標準入力に入力が詰まってるかどうかを調べるには eof を使えばいいらしい

よく、標準入力を使って:

% ps ax | grep hoge

とかやるけど、Perl6 ではどーやるんだろーと思ったので、IO::Handle を調べていたら、 &eof を使えばいいっぽいということがわかったので、すくりぷよ書いてみた:

#!/usr/bin/perl6
# -*- mode: perl6; -*-
# .. file:: sink
use v6;

sub MAIN() {
    unless $*IN.eof {
        say $*IN.slurp.chomp;
    }

}

名前はてけとー。
標準入力が詰まってない場合は何もしないので、何かを入力すると何かする。
こんな風に使う:

% echo Hello, World! | ./sink
Hello, World!

MoarVM の Configure.pl を Perl6 で書こうとした話

Perl が書けなかったので、MoarVM の ./Configure.pl を Perl6 で書いて理解してみようと思った:

use v6;

use Getopt::Long;

constant $NAME    = 'moar';
constant $GENLIST = 'build/gen.list';

sub MAIN() {
    my Bool $failed = False;

    my %args;
    my %defaults;
    my %config;
    
    get-options( %args, <help|?
        debug:s optimize:s instrument! coverage 
        os=s shell=s toolchain=s compiler=s
        ar=s cc=s ld=s make=s has-sha has-libuv
        static has-libtommath has-libatomic_ops
        has-dyncall has-libffi pkgconfig=s
        build=s host=s big-endian jit! enable-jit
        prefix=s bindir=s libdir=s mastdir=s make-install asan ubsan valgrind telemeh> );

    say $args;

    # pod2usage( True ) $;

    # say 'Welcome to MoarVM!';
}

そもそも、Perl6 の Getopt::Long の definition が PerlGetopt::Long と違う可能性が微レ存だったので、いきなりそこで躓いた。
それに、Perl6 の Getopt::Long は = が必須みたいなので、PerlGetopt::Long とは全然違う感じがする。

use v6;

use Getopt::Long;
# 
# Perl5 の Getopt::Long のように実装されているなら、以下が動くはず:
# 
my $capture = Getopt::Long.new( <help|?> );
# 
# 今のところ、動かない………。
# 
say $capture;

Getopt::Tiny というのもあるんだけど、それも真偽値は =! が必要。

use v6;

use Getopt::Tiny;

my Hash $opts = {help => False, version => False};

get-options( $opts, <help=! verbose=!> );

say $opts;

ということで、Getopt::OldFashion を書くことにした。
こんな風に書けるはず:

use v6;

use Getopt::OldFashion;

my %opts = get-options( <help|? version verbose> );

say %opts;

すごくシンプル過ぎるビルドシステム書いた

2 日も空いちゃったけど、書いた。
いつしか加わってしまった Emacsmarkdown-mode の末尾の半角スペース 2 つが 2 つのアンダーバーになってしまう クソ現象で markdown-mode が使えないため、text-mode を使っている。
と思ったら、Emacs を再起動したところ、治った。

そんなことはともかく、こういうへろーわーるどな C 言語のソースコードコンパイルしていきたい。

// examples/hello.c
#include <stdio.h>

int main() {
    printf( "Hello, World!" );

    return 0;
}

こういう Build.p6 をソースツリーのトップに置き、このファイルを叩くことで基本的にビルドを行うことができる:

# examples/Build.p6
use v6;

use IO::Glob;
use Build::Simple;

my @sources = glob( './*.c' );

my @objects = gather for @sources -> $source {
    take compile $source;
}

link 'hello', |@objects;

&compile&linkBuild::Simple モジュールに入っている。
お好みで IO::Globuse するととっても便利である。
叩く時は以下のようにする:

% perl6 -I../lib ./Build.p6

-I../lib は perl6 が lib/Build/Simple.pm6 を探しやすくするために必要である。
が、perl6/ecosystem に入った暁には zef install Build::Simple すれば必要なくなる。

ということで、lib/Build/Simple.pm6 は以下の通り。
今回は is export と書くことで Build::Simple::compile と書かなくて良くなるということがわかった。

# lib/Build/Simple.pm6
use v6;

unit module Build::Simple;
    
sub compile($dependency) is export {
    my $target = $dependency.extension: 'o';

    say "compiling {$dependency}";
    shell [ 'gcc', '-c', $dependency, '-o', $target ].join(' ');

    $target;
}

sub link($target, *@objects) is export {
    say "linking {$target}";
    shell [ 'gcc', |@objects, '-o', $target ];
}

%*ENV<CC> とかでコンパイラのコマンドを環境変数からとってきたりしたいなーなどと思っている。
ということで、また次回。

Perl6 の参照の話

というわけで、アドベントカレンダーを登録してないけどそんな気持ちでクソエントリを書いていこうと思う。

前もやったかもしれないけど、Perl6 の参照の話をしよう。

my @puyo = (1, 2, 3);

簡単な配列を入れた @puyo を宣言する。
そして、それを $myon に入れる。

my $myon = @puyo;

ここで、$myon は何を持っているだろう?それは、@puyo が持っている配列の参照だ。
えーと、配列の参照って?

╰( ´◔ ω ◔ `)╯ わからん

じゃあ、$myon をまた別の配列変数に入れてみよう:

my @hunyo = $myon;

ここで @hunyo@puyo は等価だろうか?
say で中身を表示してみよう:

[[1 2 3]]

違う。
@hunyo の中身は [[1, 2, 3]] となぜかネストした配列になっている。
これは、$myon には配列そのものではなく、配列のリファレンスが入っていることが原因だ((おそらくそうなんだろうなと言う意味))。

別の表現をすると、配列型の変数には配列しか設定できないため、スカラー変数の値を配列変数に設定した場合、暗黙のキャストが起こり、配列は配列の配列へと変容してしまう。
これを防ぐには、変数の前に | (パイプ)を付ける:

my @hunyo = |$myon;

このような現象は以下のように Map の値が配列であるというような場合に起こりやすい:

my  %already  = (
    authors => ['John Due']
);

{
    my @authors = do if %already<authors>:exists {
        %already<authors>                             # %already<authors> はスカラーを返す! パイプを使え!
    } else {
        []
    };

    say @authors;                                     # ウボァーーーーー!!!! @authors は [['John Due']] だ!!!!
}

私はJSON を扱う際にこの現象にぶち当たり、うぎゃーーーーーとなった。
みなさんも Perl6 を書く時には注意して欲しい。

虚無い

虚無(きょむ)い。
あるいは nil い。
つまるところ、こういう風に書いたら:

(null? me)

#t が返ってきそうな塩梅ということになる。

これは最近というわけでもなく、だいぶ前からだということになるんだけれども、例外が飛び出るわけでもなく、あまり問題がなさそうなのでほっといている。
まあ、問題はあるんだけれども。

………という風にクソエントリを書くと次のエントリを書く敷居というものが大幅に下がるため、このように書いているというわけだ。

なんと Perl6 ではホームディレクトリを表すチルダを変換してくれない

嘘だろ?! って思ったんだけど、ほんとっぽい。
~/.bashrc/home/alice/.bashrc って変換してくれるようなバンドルされてるメソッドとか関数がないって意味ね。

# -*- mode: per6; -*-
# \file: 00-childa.t
use v6;

use Test;

plan 2;

is $*HOME.add( '.bashrc' ).Str, '~/.bashrc'.IO.absolute, '.absolute でチルダを変換できるだろ、常識的に考えて……';
is $*HOME.add( '.bashrc' ).Str, '~/.bashrc'.IO.resolve , '.resolve でチルダを変換できるだろ、常識的に考えて……';

done-testing;

これを実行してみると:

% perl6 ./00-childa.t
1..2
not ok 1 - .absolute でチルダを変換できるだろ、常識的に考えて……

# Failed test '.absolute でチルダを変換できるだろ、常識的に考えて……'
# at ./00-childa.t line 8
# expected: '/home/rihine/workspace/p6-midi-converter-demo/~/.bashrc'
#      got: '/home/rihine/.bashrc'
not ok 2 - .resolve でチルダを変換できるだろ、常識的に考えて……

# Failed test '.resolve でチルダを変換できるだろ、常識的に考えて……'
# at ./00-childa.t line 9
# expected: '/home/rihine/workspace/p6-midi-converter-demo/~/.bashrc'
#      got: '/home/rihine/.bashrc'
# Looks like you failed 2 tests of 2

グワーーーーーーーーーーーーーーーーーーー!!!!!!

ちょー簡単な &expand-filepath はこんな感じかもだ:

sub expand-filepath($filepath) {
    $filepath.subst( '~', $*HOME.Str )
}

.subst とかで変換できるんだし、無くてよくね?って感じなのかもしれない。

NativeCall つらい

GLib とか Gtk を NativeCall で呼び出そうとしてるんだけど、つらい。

C 言語でやるような typedef はどーやんの?

GLib とかでこーいう感じの gsize は:

typedef    unsinged long    gsize;

Perl6 では以下のように constant る。

constant gsize = uint32;

構造体ってどーすんの?

Perl6 Document に書いてあるけど、こんな風に class として書くよ!書くよ!

class GtkApplication is repr( 'CStruct' ) {
    has GtkApplication $.parent;
    
    has Pointer        $.priv;
}

列挙体を渡したい時は?

わからん。
こんな風にやったら、ダメだった:

enum GApplicationFlags (
    G_APPLICATION_FLAGS_NONE => 0
);

sub gtk_application_new(Str, GApplicationFlags) returns Pointer[GtkApplication] is native('gtk-3') { * }

is repr('CEnum') とかできるんだろーか?

Perl6 の正規表現は不思議なんじゃなくて、自由だった

ここ を見て Perl6 のコードに移植するっていうのをやっていた。
Perl6 の正規表現が不思議すぎるなーと思って書いていたんだけど、よく読んだら自由だったらしい。

function isNumber (x) {
    if ( typeof x === 'number' )      return true;
    if ( /^0x[0-9a-f]+$/i.test( x ) ) return true;
    return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test( x );
}

は Perl6 で書くと多分こうなる:

sub is-number($x) {
    return True if $x.WHAT ~~ Numeric;
    return True if $x      ~~ /^0x<[0..9a..fA..F]>+$/;

    $x ~~ /^<[-+]>?(<:digit>+)?(\.<:digit>*)?(e<[-+]>?<:digit>+)?$/;
}

ʕ•͡ω•ʔ 眠い

GMake サバイバルガイド 便利そうな関数編

make は前回の記事で十分使えるようになったんじゃないかと思います*1
私も険しいまけふぁいるの坂を駆け上がっている途中なので、ここで紹介できる関数は 2 つしかないのですが、
ええい、たらーん!!! という方もおられると思います。
そのような方はGNU make: Functionsをご覧ください。

notdir 関数

notdir 関数は実際 basename コマンドと一緒です。

# makefile
ref0 = ./packages/FParsec.1.0.2/lib/net40-client/FParsec.dll
ref1 = ./packages/FParsec.1.0.2/lib/net40-client/FParsecCS.dll

target-dir = ./bin/Debug

all:
    echo $(target-dir)/$(notdir $(ref0))

以上のファイルを makefileMakefile という名前でどこか空のディレクトリに保存し、make と打ってみてください。
以下のように表示されるはずです:

% make
echo ./bin/Debug/FParsec.dll
./bin/Debug/FParsec.dll

shell 関数

shell 関数はバッククォーテーション文字列で括ったコマンドっぽいことをするようなやつです。

shell 関数を知る前はこのように書いていましたが:

SDL2_INCLUDES  = `pkg-config --cflags-only-I sdl2`
SDL2_DEFINES   = `pkg-config --cflags-only-other sdl2`
SDL2_LIBS      = `pkg-config --libs sdl2)

SDL2GFX_LIBS   = `pkg-config --libs SDL2_gfx`

shell 関数を使うとこのように書けます:

SDL2_INCLUDES  = $(shell pkg-config --cflags-only-I sdl2)
SDL2_DEFINES   = $(shell pkg-config --cflags-only-other sdl2)
SDL2_LIBS      = $(shell pkg-config --libs sdl2)

SDL2GFX_LIBS   = $(shell pkg-config --libs SDL2_gfx)

バッククォーテーションで括ったところはコマンドは無くなって、例えば pkg-config --cflags-only-I sdl2 の結果が SDL2_INCLUDES にそのまま設定されます。 ビルドしてみると、このように出力されます:

% make -B
gcc -W -Wall -O2 -D_REENTRANT -I/usr/include/SDL2 -c main.c -o main.o
main.c: 関数 ‘main’ 内:
main.c:12:14: 警告: unused parameter ‘argc’ [-Wunused-parameter]
 int main(int argc, char ** argv) {
              ^~~~
main.c:12:28: 警告: unused parameter ‘argv’ [-Wunused-parameter]
 int main(int argc, char ** argv) {
                            ^~~~
gcc -lSDL2_gfx -lSDL2 -lSDL2 main.o -o demo-sdl2-gfx

実際にやってみたい方は ( ノ╹◡◡╹)ノ SDL2_gfx を使ったデモっぽいやつ をご覧ください。

参考

*1:多分、きっと、おそらく