internal/jsre: move runtime creation back into JSRE and clean up API

This commit is contained in:
Felix Lange 2020-01-22 01:56:20 +01:00
parent e230602627
commit fd422db34f
3 changed files with 47 additions and 59 deletions

View file

@ -20,12 +20,10 @@ import (
"os" "os"
"reflect" "reflect"
"testing" "testing"
"github.com/dop251/goja"
) )
func TestCompleteKeywords(t *testing.T) { func TestCompleteKeywords(t *testing.T) {
re := New("", os.Stdout, goja.New()) re := New("", os.Stdout)
re.Run(` re.Run(`
function theClass() { function theClass() {
this.foo = 3; this.foo = 3;

View file

@ -36,14 +36,13 @@ var (
Web3Js = deps.MustAsset("web3.js") Web3Js = deps.MustAsset("web3.js")
) )
/* // JSRE is a JS runtime environment embedding the goja interpreter.
JSRE is a generic JS runtime environment embedding the goja JS interpreter. // It provides helper functions to load code from files, run code snippets
It provides some helper functions to // and bind native go objects to JS.
- load code from files //
- run code snippets // The runtime runs all code on a dedicated event loop and does not expose the underlying
- require libraries // goja runtime directly. To use the runtime, call JSRE.Do. When binding a Go function,
- bind native go objects // use the Call type to gain access to the runtime.
*/
type JSRE struct { type JSRE struct {
assetPath string assetPath string
output io.Writer output io.Writer
@ -53,6 +52,12 @@ type JSRE struct {
vm *goja.Runtime vm *goja.Runtime
} }
// Call is the argument type of Go functions which are callable from JS.
type Call struct {
goja.FunctionCall
VM *goja.Runtime
}
// jsTimer is a single timer instance with a callback function // jsTimer is a single timer instance with a callback function
type jsTimer struct { type jsTimer struct {
timer *time.Timer timer *time.Timer
@ -68,17 +73,17 @@ type evalReq struct {
} }
// runtime must be stopped with Stop() after use and cannot be used after stopping // runtime must be stopped with Stop() after use and cannot be used after stopping
func New(assetPath string, output io.Writer, vm *goja.Runtime) *JSRE { func New(assetPath string, output io.Writer) *JSRE {
re := &JSRE{ re := &JSRE{
assetPath: assetPath, assetPath: assetPath,
output: output, output: output,
closed: make(chan struct{}), closed: make(chan struct{}),
evalQueue: make(chan *evalReq), evalQueue: make(chan *evalReq),
stopEventLoop: make(chan bool), stopEventLoop: make(chan bool),
vm: vm, vm: goja.New(),
} }
go re.runEventLoop() go re.runEventLoop()
re.Set("loadScript", re.loadScript) re.Set("loadScript", MakeCallback(re.vm, re.loadScript))
re.Set("inspect", re.prettyPrintJS) re.Set("inspect", re.prettyPrintJS)
return re return re
} }
@ -244,21 +249,7 @@ func (re *JSRE) Exec(file string) error {
if err != nil { if err != nil {
return err return err
} }
var script *goja.Program return re.Compile(file, string(code))
re.Do(func(vm *goja.Runtime) {
script, err = goja.Compile(file, string(code), false)
if err != nil {
return
}
_, err = vm.RunProgram(script)
})
return err
}
// Bind assigns value v to a variable in the JS environment
// This method is deprecated, use Set.
func (re *JSRE) Bind(name string, v interface{}) error {
return re.Set(name, v)
} }
// Run runs a piece of JS code. // Run runs a piece of JS code.
@ -267,40 +258,25 @@ func (re *JSRE) Run(code string) (v goja.Value, err error) {
return v, err return v, err
} }
// Get returns the value of a variable in the JS environment.
func (re *JSRE) Get(ns string) (v goja.Value) {
re.Do(func(vm *goja.Runtime) { v = vm.Get(ns) })
return v
}
// Set assigns value v to a variable in the JS environment. // Set assigns value v to a variable in the JS environment.
func (re *JSRE) Set(ns string, v interface{}) (err error) { func (re *JSRE) Set(ns string, v interface{}) (err error) {
re.Do(func(vm *goja.Runtime) { vm.Set(ns, v) }) re.Do(func(vm *goja.Runtime) { vm.Set(ns, v) })
return err return err
} }
// loadScript executes a JS script from inside the currently executing JS code. // MakeCallback turns the given function into a function that's callable by JS.
func (re *JSRE) loadScript(call goja.FunctionCall) goja.Value { func MakeCallback(vm *goja.Runtime, fn func(Call) (goja.Value, error)) goja.Value {
file := call.Argument(0).ToString().String() return vm.ToValue(func(call goja.FunctionCall) goja.Value {
file = common.AbsolutePath(re.assetPath, file) result, err := fn(Call{call, vm})
source, err := ioutil.ReadFile(file) if err != nil {
if err != nil { panic(vm.NewGoError(err))
// Panicking with a goja.Value arg will cause a JS exception }
// in the caller. return result
panic(re.vm.ToValue(fmt.Sprintf("Could not read file %s: %v", file, err))) })
}
value, err := compileAndRun(re.vm, file, string(source))
if err != nil {
panic(re.vm.ToValue(fmt.Sprintf("Error while compiling or running script: %v", err)))
}
return value
} }
// Evaluate executes code and pretty prints the result to the specified output // Evaluate executes code and pretty prints the result to the specified output stream.
// stream. func (re *JSRE) Evaluate(code string, w io.Writer) {
func (re *JSRE) Evaluate(code string, w io.Writer) error {
var fail error
re.Do(func(vm *goja.Runtime) { re.Do(func(vm *goja.Runtime) {
val, err := vm.RunString(code) val, err := vm.RunString(code)
if err != nil { if err != nil {
@ -310,7 +286,6 @@ func (re *JSRE) Evaluate(code string, w io.Writer) error {
} }
fmt.Fprintln(w) fmt.Fprintln(w)
}) })
return fail
} }
// Compile compiles and then runs a piece of JS code. // Compile compiles and then runs a piece of JS code.
@ -319,6 +294,21 @@ func (re *JSRE) Compile(filename string, src string) (err error) {
return err return err
} }
// loadScript loads and executes a JS file.
func (re *JSRE) loadScript(call Call) (goja.Value, error) {
file := call.Argument(0).ToString().String()
file = common.AbsolutePath(re.assetPath, file)
source, err := ioutil.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("Could not read file %s: %v", file, err)
}
value, err := compileAndRun(re.vm, file, string(source))
if err != nil {
return nil, fmt.Errorf("Error while compiling or running script: %v", err)
}
return value, nil
}
func compileAndRun(vm *goja.Runtime, filename string, src string) (goja.Value, error) { func compileAndRun(vm *goja.Runtime, filename string, src string) (goja.Value, error) {
script, err := goja.Compile(filename, src, false) script, err := goja.Compile(filename, src, false)
if err != nil { if err != nil {

View file

@ -50,7 +50,7 @@ func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) {
t.Fatal("cannot create test.js:", err) t.Fatal("cannot create test.js:", err)
} }
} }
jsre := New(dir, os.Stdout, goja.New()) jsre := New(dir, os.Stdout)
return jsre, dir return jsre, dir
} }
@ -102,10 +102,10 @@ func TestNatto(t *testing.T) {
} }
func TestBind(t *testing.T) { func TestBind(t *testing.T) {
jsre := New("", os.Stdout, goja.New()) jsre := New("", os.Stdout)
defer jsre.Stop(false) defer jsre.Stop(false)
jsre.Bind("no", &testNativeObjectBinding{vm: jsre.vm}) jsre.Set("no", &testNativeObjectBinding{vm: jsre.vm})
_, err := jsre.Run(`no.TestMethod("testMsg")`) _, err := jsre.Run(`no.TestMethod("testMsg")`)
if err != nil { if err != nil {