LoginSignup
18
15

More than 5 years have passed since last update.

Javascript on Go

Last updated at Posted at 2015-02-07

最近Gradleの便利さに感動しました。設定ファイルをスクリプト言語で記述するのは大変やりやすいものです。
C/C++のようなコンパイル言語からLuaのようなスクリプト言語を利用するのはよくある手段ですが、Goでもスクリプトとやりとりする美味い方法がないか調べてみました。

望む機能

  1. go buildしたアプリケーションから、スクリプトを呼び出せる
    1. スクリプトファイルを書き換えた場合、goを再コンパイルしなくても更新内容が反映される
    2. goアプリケーション内で利用している構造体のプロパティーに、スクリプトからアクセスできる
  2. スクリプトから、goアプリケーション内のメソッドを呼び出せる
  3. 実行効率? 知らんなあ

試す言語

Goで実装されたJavaScriptエンジンottoを用いると、とても簡単に上の機能を得ることが出来ます。

robertkrimen/otto - Github

その他にはJavaScript(v8 engine)、Python、Lua、@mattn 氏制作のGo製スクリプトAnkoなどの選択肢がありそうです。

実装

main.go
package main

import (
        "fmt"
        "github.com/robertkrimen/otto"
)

// スクリプトに渡すデータ・メソッドの定義
type Property struct {
        num1  int
        Num2  int
        Name  string
        Greet func() string
        Sum   func(call otto.FunctionCall) otto.Value
}

func (p *Property) Greet2() string {
        return "Hello, " + p.Name
}

func main() {
        prop := Property{
                num1: 12345,
                Num2: 45678,
                Name: "John Doe",
                Greet: func() string {
                        return "Hello"
                },
                Sum: func(call otto.FunctionCall) otto.Value {
                        x, _ := call.Argument(0).ToInteger()
                        y, _ := call.Argument(1).ToInteger()
                        result, _ := otto.ToValue(x + y)
                        return result
                },
        }

        fmt.Println("--- Go : スクリプト実行前 ---")
        fmt.Println(prop)
        fmt.Println("-----------------------------")

        vm := otto.New()
        script, err := vm.Compile("script.js", nil)
        if err != nil {
                panic(err)
        }

        fmt.Println("---- JS : スクリプト実行 ----")
        vm.Set("prop", &prop)   // ... 変数propをスクリプト内でも使えるように設定
        _, vm_err := vm.Run(script)
        fmt.Println("---- JS : エラー出力 --------")
        if vm_err != nil {
                fmt.Println(vm_err)
        }
        fmt.Println("-----------------------------")

        fmt.Println("--- Go : スクリプト実行後 ---")
        fmt.Println(prop)
        fmt.Println(prop.Name)
        fmt.Println("-----------------------------")

}

script.js
// 設定したプロパティの一覧を表示
for(var p in prop){
  console.log(p + " : "  + prop[p]);
}

// プロパティに設定したメソッドの実行
console.log(prop.Greet());
console.log(prop.Greet2());
console.log(prop.Sum(3, 10));

// プロパティの変更
prop.Name = "ナナシノゴンベイ";
console.log(prop.Greet2());

// もとのプロパティにはないので、変更してもGoにわたらない
prop.Name2 = "ナナシノゴンベイ";
$ go build main.go
$./main
--- Go : スクリプト実行前 ---
{12345 45678 John Doe 0x401890 0x4018b0}
-----------------------------
---- JS : スクリプト実行 ----
Num2 : 45678
Name : John Doe
Greet : function () { [native code] }
Sum : function () { [native code] }
Greet2 : function () { [native code] }
Hello
Hello, John Doe
13
Hello, ナナシノゴンベイ
---- JS : エラー出力 --------
-----------------------------
--- Go : スクリプト実行後 ---
{12345 45678 ナナシノゴンベイ 0x401890 0x4018b0}
ナナシノゴンベイ
-----------------------------

スクリプトが実行され、スクリプト内で変更したプロパティがGoプログラムの中でも反映されています。
スクリプトの内容だけ変えて、再度実行したらどうなるでしょうか。

$ vim script.js
:s/ナナシノゴンベイ/名無しの権兵衛/g
$./main
--- Go : スクリプト実行前 ---
{12345 45678 John Doe 0x401890 0x4018b0}
-----------------------------
---- JS : スクリプト実行 ----
Num2 : 45678
Name : John Doe
Greet : function () { [native code] }
Sum : function () { [native code] }
Greet2 : function () { [native code] }
Hello
Hello, John Doe
13
Hello, 名無しの権兵衛
---- JS : エラー出力 --------
-----------------------------
--- Go : スクリプト実行後 ---
{12345 45678 名無しの権兵衛 0x401890 0x4018b0}
名無しの権兵衛
-----------------------------

prop.Nameを「名無しの権兵衛」に置き換えてくれています!

補足

  • Property構造体のnum1は隠蔽されているのでスクリプトからは参照できません
  • Goで変数propをスクリプト内でも使えるように設定している部分は、ポインタ渡しをしないとスクリプト内で値の変更が出来ません。参照・メソッドの実行はできるので、スクリプト内からGoへの副作用を防ぎたいときはあえて値渡しするという手もあるかも。
  • 上の例では、引数がないメソッド(Greet, Greet2)を作っていますが、これらメソッドを引数付きで呼び出すとGoのpanicが発生します。Sumメソッドのようにotto.FunctionCallを引数に取るほうが良いです。
  • int型の変数(Num2)に「あいうえお」をスクリプトで代入すると結果は0になります。文字列「111」のように数値変換可能ならばGo側では数値として代入することが出来ます。
  • Goでimport _ "github.com/robertkrimen/otto/underscore"することで、スクリプトでunderscore.jsが利用できます

今回はGoの設定をスクリプトでしたいという動機がありましたが、Goでゲームやその他のアプリケーションを作る際に拡張部分にottoを使うのもありな感じですね。
otto便利!

参考・関連

18
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
15