Skip to content

kirillDanshin/go-monkey

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

What is

This package is SpiderMonkey wrapper for Go.

You can use this package to embed JavaScript into your Go program.

It has a rich API and can be freely used in any multi-goroutine Go program.

Forked from github.com/chrisfarms/monkey.

Install

You need install SpiderMonkey first.

Mac OS X:

brew install spidermonkey

Ubuntu:

sudo apt-get install libmozjs185-dev

Or compile by yourself (reference).

And then install Monkey by "go get" command.

go get github.com/kirillDanshin/monkey

It requires Go 1.5. If you want to use newer version, run your program with GODEBUG=cgocheck=0. I'm working on fix.

Performance

There are some benchmark test in "monkey_test.go".

You can run those test like this:

go test -bench="."

The benchmark result on my Mac:

Benchmark_ADD_IN_JS           200000     13410 ns/op
Benchmark_ADD_BY_JS           200000     15265 ns/op
Benchmark_ADD_BY_GO           100000     24779 ns/op
Benchmark_OOXX_IN_JS           10000    271113 ns/op
Benchmark_OOXX_IN_GO           50000     57133 ns/op
Benchmark_OOXX_BY_GO           20000     81031 ns/op
Benchmark_ADD_IN_JS_IN_USE   1000000      1446 ns/op
Benchmark_ADD_BY_JS_IN_USE   1000000      1427 ns/op
Benchmark_ADD_BY_GO_IN_USE    500000      7139 ns/op
Benchmark_OOXX_IN_JS_IN_USE    10000    262562 ns/op
Benchmark_OOXX_BY_GO_IN_USE    50000     63353 ns/op

Examples

All the example codes can be found in "examples" folder.

You can run all of the example codes like this:

go run examples/hello_world.go

Hello World

The "hello_world.go" shows what Monkey can do.

package main

import "fmt"
import js "github.com/kirillDanshin/monkey"

func main() {
    // Create script runtime
    runtime := js.NewRuntime(8 * 1024 * 1024)

    // Create script context
    context := runtime.NewContext()

    // Evaluate script
    value := context.Eval("'Hello ' + 'World!'")
    println(value.ToString())

    // Define a function and call it
    context.DefineFunction("println", func(f *js.Func) {
        for i := 0; i < f.Argc(); i++ {
            fmt.Print(f.Argv(i))
        }
        fmt.Println()
        f.Return(f.Context().Void())
    })
    context.Eval("println('Hello Function!')")

    // Compile once, run many times
    script := context.Compile(
        "println('Hello Compiler!')",
        "<no name>", 0,
    )
    script.Execute()
    script.Execute()
    script.Execute()

    // Error handler
    context.SetErrorReporter(func(report *js.ErrorReport) {
        println(fmt.Sprintf(
            "%s:%d: %s",
            report.FileName, report.LineNum, report.Message,
        ))
        if report.LineBuf != "" {
            println("\t", report.LineBuf)
        }
    })
    context.Eval("not_exists()")
}

This code will output:

Hello World!
Hello Function!
Hello Compiler!
Hello Compiler!
Hello Compiler!
Eval():0: ReferenceError: not_exists is not defined

Thread Safe

The "many_many.go" shows Monkey is thread safe.

package main

import "fmt"
import "sync"
import "runtime"
import js "github.com/kirillDanshin/monkey"

func assert(c bool) bool {
    if !c {
        panic("assert failed")
    }
    return c
}

func main() {
    runtime.GOMAXPROCS(20)

    // Create script runtime
    runtime := js.NewRuntime(8 * 1024 * 1024)

    // Create script context
    context := runtime.NewContext()

    context.DefineFunction("println", func(f *js.Func) {
        for i := 0; i < f.Argc(); i++ {
            fmt.Print(f.Argv(i))
        }
        fmt.Println()
        f.Return(f.Context().Void())
    })

    wg := new(sync.WaitGroup)

    // One runtime instance used by many goroutines
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            for j := 0; j < 100; j++ {
                v := context.Eval("println('Hello World!')")
                assert(v != nil)
            }
            wg.Done()
        }()
    }

    wg.Wait()
}

Value

The "op_value.go" shows how to convert JS value to Go value.

package main

import js "github.com/kirillDanshin/monkey"

func assert(c bool) bool {
    if !c {
        panic("assert failed")
    }
    return c
}

func main() {
    // Create script Runtime
    runtime := js.NewRuntime(8 * 1024 * 1024)

    // Create script context
    context := runtime.NewContext()

    // String
    if value := context.Eval("'abc'"); assert(value != nil) {
        assert(value.IsString())
        assert(value.ToString() == "abc")
    }

    // Int
    if value := context.Eval("123456789"); assert(value != nil) {
        assert(value.IsInt())

        if value1, ok1 := value.ToInt(); assert(ok1) {
            assert(value1 == 123456789)
        }
    }

    // Number
    if value := context.Eval("12345.6789"); assert(value != nil) {
        assert(value.IsNumber())

        if value1, ok1 := value.ToNumber(); assert(ok1) {
            assert(value1 == 12345.6789)
        }
    }
}

Function

The "op_func.go" shows how to play with JS function value.

package main

import js "github.com/kirillDanshin/monkey"

func assert(c bool) bool {
    if !c {
        panic("assert failed")
    }
    return c
}

func main() {
    // Create script runtime
    runtime := js.NewRuntime(8 * 1024 * 1024)

    // Create script context
    context := runtime.NewContext()

    // Return a function object from JavaScript
    if value := context.Eval("function(a,b){ return a+b; }"); assert(value != nil) {
        // Type check
        assert(value.IsFunction())

        // Call
        value1 := value.Call([]*js.Value{
            context.Int(10),
            context.Int(20),
        })

        // Result check
        assert(value1 != nil)
        assert(value1.IsNumber())

        if value2, ok2 := value1.ToNumber(); assert(ok2) {
            assert(value2 == 30)
        }
    }

    // Define a function that return an object with function from Go
    ok := context.DefineFunction("get_data", func(f *js.Func) {
        obj := f.Context().NewObject(nil)

        ok := obj.DefineFunction("abc",
            func(object *js.Object, name string, args []*js.Value) *js.Value {
                return f.Context().Int(100)
            },
        )

        assert(ok)

        f.Return(obj.ToValue())
    })

    assert(ok)

    if value := context.Eval(`
        a = get_data();
        a.abc();
    `); assert(value != nil) {
        assert(value.IsInt())

        if value2, ok2 := value.ToInt(); assert(ok2) {
            assert(value2 == 100)
        }
    }
}

Array

The "op_array.go" shows how to play with JS array.

package main

import js "github.com/kirillDanshin/monkey"

func assert(c bool) bool {
    if !c {
        panic("assert failed")
    }
    return c
}

func main() {
    // Create script runtime
    runtime := js.NewRuntime(8 * 1024 * 1024)

    // Create script context
    context := runtime.NewContext()

    // Return an array from JavaScript
    if value := context.Eval("[123, 456];"); assert(value != nil) {
        // Check type
        assert(value.IsArray())
        array := value.ToArray()
        assert(array != nil)

        // Check length
        assert(array.GetLength() == 2)

        // Check first item
        value1, ok1 := array.GetInt(0)
        assert(ok1)
        assert(value1 == 123)

        // Check second item
        value2, ok2 := array.GetInt(1)
        assert(ok2)
        assert(value2 == 456)

        // Set first item
        assert(array.SetInt(0, 789))
        value3, ok3 := array.GetInt(0)
        assert(ok3)
        assert(value3 == 789)

        // Grows
        assert(array.SetLength(3))
        assert(array.GetLength() == 3)
    }

    // Return an array from Go
    if ok := context.DefineFunction("get_data", func(f *js.Func) {
        array := f.Context().NewArray()
        array.SetInt(0, 100)
        array.SetInt(1, 200)
        f.Return(array.ToValue())
    }); assert(ok) {
        if value := context.Eval("get_data()"); assert(value != nil) {
            // Check type
            assert(value.IsArray())
            array := value.ToArray()
            assert(array != nil)

            // Check length
            assert(array.GetLength() == 2)

            // Check first item
            value1, ok1 := array.GetInt(0)
            assert(ok1)
            assert(value1 == 100)

            // Check second item
            value2, ok2 := array.GetInt(1)
            assert(ok2)
            assert(value2 == 200)
        }
    }
}

Object

The "op_object.go" shows how to play with JS object.

package main

import js "github.com/kirillDanshin/monkey"

func assert(c bool) bool {
    if !c {
        panic("assert failed")
    }
    return c
}

func main() {
    // Create script runtime
    runtime := js.NewRuntime(8 * 1024 * 1024)

    // Create script context
    context := runtime.NewContext()

    // Return an object from JavaScript
    if value := context.Eval("x={a:123}"); assert(value != nil) {
        // Type check
        assert(value.IsObject())
        obj := value.ToObject()

        // Get property 'a'
        value1, ok1 := obj.GetInt("a")
        assert(ok1)
        assert(value1 == 123)

        // Set property 'b'
        assert(obj.SetInt("b", 456))
        value2, ok2 := obj.GetInt("b")
        assert(ok2)
        assert(value2 == 456)
    }

    // Return and object From Go
    ok := context.DefineFunction("get_data", func(f *js.Func) {
        obj := f.Context().NewObject(nil)
        obj.SetInt("abc", 100)
        obj.SetInt("def", 200)
        f.Return(obj.ToValue())
    })

    assert(ok)

    if value := context.Eval("get_data()"); assert(value != nil) {
        // Type check
        assert(value.IsObject())
        obj := value.ToObject()

        // Get property 'abc'
        value1, ok1 := obj.GetInt("abc")
        assert(ok1)
        assert(value1 == 100)

        // Get property 'def'
        value2, ok2 := obj.GetInt("def")
        assert(ok2)
        assert(value2 == 200)
    }
}

Property

The "op_prop.go" shows how to handle JavaScript object's property in Go.

package main

import js "github.com/kirillDanshin/monkey"

func assert(c bool) bool {
    if !c {
        panic("assert failed")
    }
    return c
}

type T struct {
    abc int32
}

func main() {
    // Create Script Runtime
    runtime := js.NewRuntime(8 * 1024 * 1024)

    // Create script context
    context := runtime.NewContext()

    // Return Object With Property Getter And Setter From Go
    ok := context.DefineFunction("get_data", func(f *js.Func) {
        cx := f.Context()
        obj := cx.NewObject(&T{123})

        // Define the property 'abc' with getter and setter
        ok := obj.DefineProperty("abc",
            // Init value
            cx.Void(),
            // T getter callback called each time
            // JavaScript code accesses the property's value
            func(g *js.Getter) {
                t := g.Object().GetPrivate().(*T)
                if g.Name() == "abc" {
                    g.Return(cx.Int(t.abc))
                } else {
                    panic("undefined property " + g.Name())
                }
            },
            // The setter callback is called each time
            // JavaScript code assigns to the property
            func(s *js.Setter) {
                t := s.Object().GetPrivate().(*T)
                if s.Name() == "abc" {
                    d, ok := s.Value().ToInt()
                    assert(ok)
                    t.abc = d
                } else {
                    panic("undefined property " + s.Name())
                }
            },
            0,
        )

        assert(ok)

        f.Return(obj.ToValue())
    })

    assert(ok)

    if value := context.Eval(`
        a = get_data();
        v1 = a.abc;
        a.abc = 456;
        v2 = a.abc;
        [v1, v2, a];
    `); assert(value != nil) {
        // Type check
        assert(value.IsArray())
        array := value.ToArray()
        assert(array != nil)

        // Length check
        assert(array.GetLength() == 3)

        // Check v1
        value1, ok1 := array.GetInt(0)
        assert(ok1)
        assert(value1 == 123)

        // Check v2
        value2, ok2 := array.GetInt(1)
        assert(ok2)
        assert(value2 == 456)

        // Check v3
        obj := array.GetObject(2)
        assert(obj != nil)
        t, ok3 := obj.GetPrivate().(*T)
        assert(ok3)
        assert(t.abc == 456)
    }

    if value := context.Eval(`
        var a = {};
        a.field1 = 1;
        a.field2 = "hello";
        a.field3 = 1.2;
        a.field4 = true;
        a.field5 = {};
        a.func1 = function(){};
        a;
    `); assert(value != nil) {
        obj := value.ToObject()
        assert(obj != nil)

        keys := obj.Keys()
        assert(len(keys) == 6)
        assert(keys[0] == "field1")
        assert(keys[1] == "field2")
        assert(keys[2] == "field3")
        assert(keys[3] == "field4")
        assert(keys[4] == "field5")
        assert(keys[5] == "func1")

        keys = obj.GetProperty("field5").ToObject().Keys()
        assert(len(keys) == 0)
    }
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors 4

  •  
  •  
  •  
  •