2014/12/25


golang には go build というビルド機能があり、C言語と golang をまぜた cgo というC言語拡張も同じコマンドでビルド出来ます。

その際、ソースコードのコメントに CFLAGS や LDFLAGS を自ら指定する事が出来るので

package gtk

// #include "gtk.go.h"
// #cgo pkg-config: gtk+-2.0
import "C"
import (
    "fmt"
    "log"
    "reflect"
    "runtime"
    "strings"
    "unsafe"

    "github.com/mattn/go-gtk/gdk"
    "github.com/mattn/go-gtk/gdkpixbuf"
    "github.com/mattn/go-gtk/glib"
    "github.com/mattn/go-gtk/pango"
)

上記は go-gtk のコードの一部。

そのライブラリやアプリケーションをビルドしたいユーザは特にライブラリへのパスを指定する事なく、ただ単に

go build

と実行するだけで実行モジュールが出来上がります。この便利さに目を付けた Pietro Gagliardi さんが qo という golang で書かれたプログラムを公開してくれています。

andlabs/qo · GitHub

Another build system for C/C++, I guess? Inspired by 'go build'

https://github.com/andlabs/qo

インストールは golang がインストールされている状態であれば

go get github.com/andlabs/qo

だけです。昔 golang のリポジトリから Makefile が消え去り ビルド構成ファイルが何もないという状況を見て軽いカルチャーショックを受けたのだけど、今となってはとても心地良いし「あー、Makefile いらんかったんやー」とも思います。その心地よさを C/C++ で扱えるツールです。例えば gtk を使ったプログラムを書く場合

#include <gtk/gtk.h>

int
main(int argc, char* argv[]) {
  GtkWidget* window;
  GtkWidget* label;
  gtk_init(&argc, &argv);
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "helloworld");
  g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, window);
  label = gtk_label_new("Hello World!");
  gtk_container_add(GTK_CONTAINER(window), label);
  gtk_widget_show_all(window);
  gtk_main();
  return 0;
}
pkg-config というツールを使いますが、このツールから得られた CFLAGS や LDFLAGS をコマンドラインに渡すか、以下の様な Makefile が必要でした。

SRCS \
    foo.c

OBJS $(subst .c,.o,$(SRCS))

CFLAGS `pkg-config --cflags gtk+-2.0`
LIBS `pkg-config --libs gtk+-2.0`
TARGET = qo-sandbox

all : $(TARGET)

$(TARGET) : $(OBJS)
    gcc -o $@ $(OBJS) $(LIBS)

.c.o :
    gcc -c $(CFLAGS) -I. $< -o $@

clean :
    rm -f *.o $(TARGET)

しかし qo を使うと Makefile は消して良く、以下の様なコメントを書くだけで良いのです。

// #qo pkg-config: gtk+-2.0
#include <gtk/gtk.h>

int
main(int argc, char* argv[]) {
  GtkWidget* window;
  GtkWidget* label;
  gtk_init(&argc, &argv);
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "helloworld");
  g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, window);
  label = gtk_label_new("Hello World!");
  gtk_container_add(GTK_CONTAINER(window), label);
  gtk_widget_show_all(window);
  gtk_main();
  return 0;
}

あとはおもむろに

qo

を実行するだけです。毎回フルビルドするのではなく .qoobj というフォルダにビルド済みのファイルが格納されるので更新されたファイルのみビルドされます。また CFLAGS や LDFLAGS も直接指定する事が出来ます。

// #qo CFLAGS: -I/opt/sqlite3/include
// #qo LIBS: sqlite3
さらに golang の go build と同様に、ファイル名に _windows が付いている物は windows のみ、_386 が付いている物は 386 系 CPU のみビルド対象となります。(サポートOS: windows, darwin, linux, freebsd, openbsd, netbsd, dragonfly, solaris)

リンクされる実行モジュールはディレクトリ名という割り切りなので、覚えてしまえば面倒臭さがなくなりとても心地よいです。

Makefile ほど細かな設定は出来ないですが、ちょっとした小さいプログラムを書きあげる際にとても便利なツールとなる事は間違いないと思います。



VimでURLをドメインだけに置換するコマンドを正規表現でうったら、本当に正規表現って意味不明なフォルムと思った。 - Qiita
http://qiita.com/mochizukikotaro/items/b15170dccb18d84f8cd2
:%s/\([:\/]\)\@<!\/.*$//g
なんか、とても意味不明なコマンドだ。きっと、もっとスマートなものがあるのだろう。コードゴルフで言えばトリプルボギー的な感じなのでしょうか。知らないけど。
この問題は
  • URL を扱うので / が多く、置換の区切りを / にする場合エスケープが多くなる
  • very magic でないのでエスケープがさらに増える

この2点から生まれます。1点目は

:%s#\([:/]\)\@<!/.*$##g
#
:%s,\([:/]\)\@<!/.*$,,g

, をセパレータにする事で少しは解消するかと思います。2点目は \v を付ける事でキャプチャ \(\)() と書く事が出来ます。

ただし今回の例はパターンが短いので効果は出ませんが、もう少し長いパターンだと効果が表れます。

あと、この例では Vim 独自の \zs を使う事でもう少しシンプルに書く事も出来ます。

:%s!//[^/]\+\zs/.*!!g
  • // で始まり
  • / で無い物が続き
  • \zs そこまでは置換対象から外す
  • /.* を置換対象とする(※1)

結果、/.*つまり URL のパス部分(※1)だけが空白に置き換えられるので URL のホスト名までが残ります。

もちろんちゃんとしたURLで判定する場合は、もう少し細かな条件が必要となります。


2014/12/24


この記事はVim Advent Calendar 2014 - Qiita 24日目の記事です。

Matz さんが streem という、ストリーム指向言語の開発を始めるらしいです。

お兄ちゃん

https://github.com/matz/streem

まだ文法の設計段階ではあるけど、それなのにかなりの量の pull-req がバンバンと来てて凄いなーと思いつつも「この pull-req 量だと僕には出番無いなー」と思ったのと「Matz さんがもしかしたら Go で streem を実装するかもしれない」という記事を読み「streem の他言語実装が一つ消えてしまう。これはまずい。」と思ったので、README.md に書かれているサンプルだけを頼りに streem を Vim script で実装してみました。

SlipStreem!

先日はネタで streem のマネをして golang で実装したりしましたが、本日ネタが無い中にどうしても Vim script で streem を実装したくなったのでやってみました。

どんなものか

とりあえずこんな事が出来ます。まず以下のバッファを用意します。

foo
bar
baz

そして以下のコマンドを実行します。

:%Streem {|x| x += " Matz" } | STDOUT

すると画面に

foo Matz
bar Matz
baz Matz

と表示されます。

どうやって動いているのか

まず、Streem コマンドは Vim の range から行を入力として扱います。つまり上記で言えば ['foo', 'bar', 'baz'] となります。

そして引数のコマンドを解析し、AST(抽象構文木)に分解します。分解に使ったマッチパターンテーブルは以下の通り。

let s:tbl = [
\  ['stmts',
\    [
\      { 'type''node''match': ['stmt', ['lb''stmt']], 'eval''s:stmts' },
\    ],
\  ],
\  ['stmt',
\    [
\      { 'type''node''match': ['expr', ['|''expr']], 'eval''s:stmt' },
\    ],
\  ],
\  ['expr',
\    [
\      { 'type''node''match': ['expr_node''sp''==''sp''expr_node'], 'eval''s:op_eqeq' },
\      { 'type''node''match': ['if''sp''expr''sp''end'], 'eval''s:expr_if' },
\      { 'type''node''match': ['ident''('')'], 'eval''s:op_call' },
\      { 'type''node''match': ['ident''(''expr_node'')'], 'eval''s:op_call' },
\      { 'type''node''match': ['ident''sp''=''sp''expr_node'], 'eval''s:op_let' },
\      { 'type''node''match': ['ident''sp''+=''sp''expr_node'], 'eval''s:op_plus' },
\      { 'type''node''match': ['expr_node', ['sp''|''sp''expr_node']], 'eval''s:expr' },
\      { 'type''node''match': ['expr_node'], 'eval''s:expr' },
\    ],
\  ],
\  ['expr_node',
\    [
\      { 'type''node''match': ['{''sp''|''ident''|''sp''}'], 'eval''s:expr_func' },
\      { 'type''node''match': ['{''sp''|''ident''|''sp''stmts''sp''}'], 'eval''s:expr_func' },
\      { 'type''node''match': ['ident'], 'eval''s:expr' },
\      { 'type''node''match': ['number'], 'eval''s:expr' },
\      { 'type''node''match': ['string'], 'eval''s:expr' },
\    ],
\  ],
\  ['string', [{ 'type''regexp''match''\("[^"]*"\|''[^'']*''\)''eval''s:expr_string' }]],
\  ['number', [{ 'type''regexp''match''[0-9]\+''eval''s:expr_number' }]],
\  ['ident', [{ 'type''regexp''match''[a-zA-Z][a-zA-Z0-9]*''eval''s:expr_ident' }]],
\  ['+=', [{ 'type''string''match''+=''eval''' }]],
\  ['=', [{ 'type''string''match''=''eval''' }]],
\  ['|', [{ 'type''string''match''|''eval''' }]],
\  ['if', [{ 'type''string''match''if''eval''' }]],
\  ['end', [{ 'type''string''match''end''eval''' }]],
\  ['sp', [{ 'type''regexp''match''[ \t]*''eval''' }]],
\  ['lb', [{ 'type''regexp''match''[ \t]*[\\r\n;]\+[ \t]*''eval''' }]],
\  ['{', [{ 'type''string''match''{''eval''' }]],
\  ['}', [{ 'type''string''match''}''eval''' }]],
\]

これを YACC っぽくパースし、各ノードに分解します。例えば += オペレータであれば以下のノードになります。

{'type''op_plus''value': [{'type''ident''value''x'}{'type''expr''value': [{'type''string''value''Matz'}]}]}

これを小さな VM (今回は時間が無くて streeem が実装している程の命令をサポート出来ませんでした)で実行します。STDOUT は入力を echo するだけのオブジェクトです。

また今回は残念ですが concurrency ではありません。メモリを使いシーケンシャルに実行しています。

ただ、これだけは覚えておいて下さい。

Vim に出来ないから実装しなかった訳ではない

やろうと思えば remote API を使い、複数立ち上げた vim と非同期通信を行いながら結果を集める事だって出来ます。

とここまで書きましたが、そろそろクドいし面倒臭くなってきたと思うので、どうやって動いているか知りたい人はソースを見てください。

mattn/streem-vim - GitHub
https://github.com/mattn/streem-vim

さいごに

さて、明日で Vim Advent Calendar 2014 が完走します(2日程抜けてしまいましたが)。皆さんお疲れ様でした。まさか開発されて20年近くにもなるテキストエディタでこれだけのブログ記事があがって来るとは誰が想像したでしょうか。

また今年も数多くの不具合報告が vim-jp へと寄せられ、数多くのバグが vim-jp によって修正されました。バグ報告頂いた皆さん、そしてパッチを書いてくれた vim-jp のメンバに感謝したいと思います。ありがとうございました。

来年もよい Vim 年になる事を祈って、僕の記事を終えさせて頂きます。

皆様、良いお年を。

ソフトウェア デザイン 2015年 01月号 [雑誌] ソフトウェア デザイン 2015年 01月号 [雑誌]

技術評論社 / (2014-12-18)
 
発送可能時間: