Go
GoDay 8

Goでランタイム時にJavaScriptを評価する

More than 5 years have passed since last update.

Go Advent Calendar 2013 8日目の記事です。

みんな大好きJavaScript、ということでGoでもランタイム時にJavaScriptを評価できるライブラリが台頭してきています。

静的型付け言語内に別の言語(スクリプト言語)を組み込んで評価するメリットというのは、作成しているソフトウェアが巨大になるにつれて、感じられるものだと思っています。

たとえば、周辺の機能を拡張したい場合、本体のコードに手をいれるのも良いけれども、そうするといろんなところでそのイレギュラーな処理を考慮しなければならなくなります。

そこで、本体側はAPIを用意しておき、拡張したい場合はそのAPIを外から触って挙動を変更する、ということはよく取られる手法だと思います。

前置きが長くなりました。ではGoでJavaScriptを評価していきましょう。


ライブラリ

もちろんGoはオフィシャルにJavaScriptをサポートしているわけではないので、サードパーティ製のライブラリを使うことになります。

以下のように複数作成されているのですが、

いろいろ触っているとOttoの完成度が高いと感じたのでその使い方を見ていきます。

ちなみに、少し前にHacker Newsで話題になったので合わせて読むと面白いです。

https://news.ycombinator.com/item?id=6812675


Ottoのインストール

$ go get github.com/robertkrimen/otto

また、ライブラリとして使う以外に、コマンドラインでJavaScriptを実行する実行ファイルも用意されています。

$ go get github.com/robertkrimen/otto/otto

これで$GOPATH/binに実行ファイルottoがビルドされます。

たとえば、

$ cat test.js

function f(x, y) {
return x + y;
}
console.log(f(1, 2));
$ otto test.js
3

のように実行できます。


hello, world

インストールができたらJavaScriptをGoで評価する準備はできました。

サンプルコードを書いてみましょう。

package main

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

func main() {
Otto := otto.New()
Otto.Run(`
var x = 2 + 2;
console.log("The value of x = " + x);
`
)
}

otto#Run() で渡したJavaScript文字列が評価されます。

console.logで計算された結果が表示されることがわかります。


何ができる?

これだと、単にJavaScriptを実行しただけになります。何も面白みはありません。

最初に述べた、うんちくを簡単に実装してみることにします。

整数で四則演算ができる計算機にしてみましょう。

そして、その計算機の挙動はユーザがカスタマイズして加えたり変更できたりするものです。

少し長いので、gistに挙げています。

https://gist.github.com/yoppi/7853138

このコードを

$ go build calc.go

$ ./calc
+-*/> 1 + 1
2

とビルドして実行するとプロンプトが表示されます。

ここで 1 + 1とか適当な文字列を入力すると計算してくれます。

そして、唯一のコマンドであるloadコマンドをjsのファイルを指定して実行することで、そのファイルで定義したものを取り込み、計算機の機能を増やすことができます。

Ottoを評価している部分を抜き出すと

func (c *Calculator) LoadScript(file string) {

Otto := otto.New()
buffer, _ := ioutil.ReadFile(file)
Otto.Run(string(buffer))
_operator, _ := Otto.Get("operator")
operator, _ := _operator.ToString()
script, _:= Otto.Get("script")
c.AddOperator(operator, func(x, y int) int {
_ret, _ := script.Call(script, x, y)
ret, _ := _ret.ToInteger()
return int(ret)
})
}

ファイルを読み込んで、一度otto.Run()で実行させて、operatorとscript宣言をGo側から取得できるようにしています。

取得したoperatorをキーにして、scriptをランタイムで実行させた結果を返す関数を登録しています。

たとえば、mod.jsというファイルを次のような内容でloadさせると、

$ cat mod.js

var operator = '%';
var script = function(x, y) {
return x % y;
}
$ ./calc
+-*/> load mod.js
+-*/%> 3 % 2
1

演算子として、%が使えるようになり、剰余を求められるようになります。

Ottoの優れているところとして、そのままオブジェクトを評価できるので、JavaScript自体に手を加えずにすみます。

そして、APIに沿って書いたJavaScriptに対してユニットテストを書くことももちろんできます。

どうでしょうか。プログラムの挙動をカスタマイズしていくのは面白いものがあります。

もちろん、プロダクトとして使えるものにするためには、なんでも実行できないようにsandboxを定義してそこだけでしか動作しないような仕組みが必要ですが、実行中にカスタマイズしていけるので、使いどころによっては便利です。


まとめ

Dockerを始めとして、Goで巨大なソフトウェアが作成されつつある流れを感じています。

巨大なソフトウェアだと細かいところまでカスタマイズを面倒見切れない、

そういうときに、静的型付け言語内でスクリプト言語をランタイム時に評価してソフトウェア自身を拡張するという一手法があり、ポピュラーな言語であるJavaScriptを選んでみました。