読者です 読者をやめる 読者になる 読者になる

ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

Go言語からmrubyのDSLを読み込んでよしなに使う

Go言語便利だけど、文法をこねくり回して内部DSLを作るとかには向いていないので、ルビー風にDSLを読み込ませて設定と替えさせていただきます、と言うのが少し難しい。

いっそGo内部でRubyを組み込んで実行したい。mrubyならできるんじゃね? と思ってやってみたらそれっぽいのができた。

Vagrant、Packer、Consulなど、DevOps対応界隈でその名を知らない者はいない mitchellh さんの

をつかう。

多分だけど、ハシモト氏も同じようなこと考えてたんじゃないだろうか。結局
hashicorp/hcl · GitHub を使われてるけれど。


完成したそれっぽいの.go

package main

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/mitchellh/go-mruby"
)

func main() {
	mrb := mruby.NewMrb()
	defer mrb.Close()

	file, err := os.Open("./Samplefile")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	data, err := ioutil.ReadAll(file)
	if err != nil {
		panic(err)
	}
	src := string(data[:])
	fmt.Printf("src is:\n%v\n", src)

	inFoobar := false
	parsedConfig := make(map[string]string)

	mrb_foobarBlock := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {
		inFoobar = true

		args := m.GetArgs()
		_, err := m.Yield(args[0])
		if err != nil {
			panic(err.Error())
		}

		inFoobar = false
		return mruby.Nil, nil
	}
	mrb_buz := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {
		if !inFoobar {
			return mruby.Nil, nil
		}
		args := m.GetArgs()
		parsedConfig["buz"] = args[0].String()

		return m.TrueValue(), nil
	}
	mrb_fizz := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {
		if !inFoobar {
			return mruby.Nil, nil
		}
		args := m.GetArgs()
		parsedConfig["fizz"] = args[0].String()

		return m.TrueValue(), nil
	}

	kernel := mrb.KernelModule()
	kernel.DefineMethod("foobar_block", mrb_foobarBlock, mruby.ArgsBlock())
	kernel.DefineMethod("buz", mrb_buz, mruby.ArgsReq(1))
	kernel.DefineMethod("fizz", mrb_fizz, mruby.ArgsReq(1))

	_, err = mrb.LoadString(src)
	if err != nil {
		panic(err.Error())
	}

	fmt.Printf("parsed data is:\n%v\n", parsedConfig)
}

Go、初心者なので、コードがひどいのは勘弁。

mruby - GoDoc を眺めて何となく書いたら大丈夫だった。ブロックも使える。たぶん、Cでmruby向けのコードを書いた経験がある人ならもっとスムーズに書けそう。

foobar_block do
  buz  "hogehoge"
  fizz "fugafuga"
end

buz  "waiwai"
fizz "wakuwaku"

みたいなRubyDSLを同じディレクトリに置いといて実行する。

$ go run sample.go
src is:
foobar_block do
  buz  "hogehoge"
  fizz "fugafuga"
end

buz  "waiwai"
fizz "wakuwaku"

parsed data is:
map[buz:hogehoge fizz:fugafuga]

buz/fizz メソッドが、ちゃんとブロックの中で実行されたときのみ有効になっている。で、mruby側のメソッド実行結果がGoでのmapに対応している。

Tips

ビルドが、コツがあって、 go run/build を実行するディレクトリと同じ所に libmruby.a を置いとかないとコンパイルできなかった。逆に、好きなmrbgemを組み込んだ libmruby.a を用意して利用することもできるみたい。

ポータビリティ的にはビルド後のバイナリに libmruby.a も丸っと組み込まれるみたいなのでビルドしてコピーすれば使えそう。逆にクロスコンパイルとか... どうするんだろう。

あと、DSLのところ全然並行性とか考えていないけど、チャンネルとか本気で使えばうまくやれるかもしれない。

ソースですが、 GitHub にも置いといたのでいじってみてください。make一発で動きます →
udzura/dsl-with-go-mruby · GitHub