golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang)でJavaScriptが実行できるottoの使い方

golang.png


otto は、JavaScript をGo言語に変換して実行できるパッケージです。
Goのプログラムをコンパイルして JavaScript のファイルを読み込めば動的に実行できるので、
設定ファイル以上のことをする場合などに便利です。
ただし ES5 なので let, const, () =>, などは使えませんし、正規表現もGo言語にない先読みなどは使えません。

同様に、Go言語やPythonの構文を動的に実行できるパッケージがあるので検索してみてください。




目次



ダウンロード

$ go get github.com/robertkrimen/otto



ファイルを読み込む - Compile

package main

import (
    "fmt"

    "github.com/robertkrimen/otto"
)

func main() {
    vm := otto.New()

    {
        // ファイルから読み込む
        script, err := vm.Compile("index.js", nil)
        if err != nil {
            fmt.Println(err)
            return
        }
        _, err = vm.Run(script)
        if err != nil {
            fmt.Println(err)
            return
        }
    }

    {
        // ファイルではなく第二引数の src が優先される
        script, err := vm.Compile("index.js", `console.log("hello")`)
        if err != nil {
            fmt.Println(err)
            return
        }

        _, err = vm.Run(script)
        if err != nil {
            fmt.Println(err)
            return
        }
    }
}



スクリプトの実行 - Run

package main

import (
    "fmt"

    "github.com/robertkrimen/otto"
)

func main() {
    vm := otto.New()

    script := `
var n = 100;
console.log("hello-" + n); // hello-100
n; // 最後に返すと Run の戻り値 value になる
`
    value, err := vm.Run(script)
    if err != nil {
        fmt.Println(err)
        return
    }

    if !value.IsUndefined() {
        fmt.Println("value:", value.String()) // 100
    }
}



Go側で変数を定義してスクリプト内で使う - Set

package main

import (
    "fmt"

    "github.com/robertkrimen/otto"
)

func main() {
    vm := otto.New()

    vm.Set("variable", "hello")

    script := `
console.log("variable: " + variable); // hello
`
    _, err := vm.Run(script)
    if err != nil {
        fmt.Println(err)
        return
    }
}



Go側で定義した構造体をスクリプト内で使う - Set

mapも同じように . で使うことができる

package main

import (
    "fmt"

    "github.com/robertkrimen/otto"
)

type User struct {
    Name string
    Age  int
}

func main() {
    vm := otto.New()

    user := User{"Tanaka", 30}
    vm.Set("user", user)

    script := `
  console.log("name: " + user.Name + ", age: " + user.Age);
  `
    _, err := vm.Run(script)
    if err != nil {
        fmt.Println(err)
        return
    }
}



レシーバ付き関数を渡す - Set

func (u *User) Profile() {
    fmt.Println("call func Profile: name:", u.Name, "age:", u.Age)
}

func main() {
    vm := otto.New()

    user := User{"Tanaka", 30}
    vm.Set("profile", user.Profile)

    script := ` profile(); `
    _, err := vm.Run(script)
    if err != nil {
        fmt.Println(err)
        return
    }
}



引数付き関数 - FunctionCall

package main

import (
    "fmt"

    "github.com/robertkrimen/otto"
)

func main() {
    vm := otto.New()

    sum := func(call otto.FunctionCall) otto.Value {
        // スクリプトの実行箇所。
        fmt.Println("Caller location:", call.CallerLocation())

        var n int64
        for _, v := range call.ArgumentList {
            i, _ := v.ToInteger()
            n += i
        }
        value, _ := otto.ToValue(n)
        return value
    }

    vm.Set("sum", sum)

    // スクリプト側で実行
    script := `
console.log(sum(10, 20, 30, 40)); // 100
`
    _, err := vm.Run(script)
    if err != nil {
        fmt.Println(err)
        return
    }
}



スクリプト内で定義した変数をGo側で使う - Get

package main

import (
    "fmt"

    "github.com/robertkrimen/otto"
)

func main() {
    vm := otto.New()

    script := `
var variable = 100;
`
    _, err := vm.Run(script)
    if err != nil {
        fmt.Println(err)
        return
    }

    value, err := vm.Get("variable")
    if err != nil {
        fmt.Println(err)
        return
    }

    if value.IsNumber() {
        n, _ := value.ToInteger()
        fmt.Println("value:", n)
    }
}



スクリプト内で定義した関数をGo側で実行 - Call

package main

import (
    "fmt"

    "github.com/robertkrimen/otto"
)

func main() {
    vm := otto.New()

    script := `
function sum(x, y) {
  return x + y;
}
`
    _, err := vm.Run(script)
    if err != nil {
        fmt.Println(err)
        return
    }

    sum, err := vm.Get("sum")
    if err != nil {
        fmt.Println(err)
        return
    }

    value, err := sum.Call(sum, 100, 200)
    if err != nil {
        fmt.Println(err)
        return
    }

    if n, err := value.ToInteger(); err == nil {
        fmt.Println("value:", n)
    }
}