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"
"reflect"
"testing"
"github.com/dop251/goja"
)
func TestCompleteKeywords(t *testing.T) {
re := New("", os.Stdout, goja.New())
re := New("", os.Stdout)
re.Run(`
function theClass() {
this.foo = 3;

View file

@ -36,14 +36,13 @@ var (
Web3Js = deps.MustAsset("web3.js")
)
/*
JSRE is a generic JS runtime environment embedding the goja JS interpreter.
It provides some helper functions to
- load code from files
- run code snippets
- require libraries
- bind native go objects
*/
// JSRE is a JS runtime environment embedding the goja interpreter.
// It provides helper functions to load code from files, run code snippets
// and bind native go objects to JS.
//
// The runtime runs all code on a dedicated event loop and does not expose the underlying
// goja runtime directly. To use the runtime, call JSRE.Do. When binding a Go function,
// use the Call type to gain access to the runtime.
type JSRE struct {
assetPath string
output io.Writer
@ -53,6 +52,12 @@ type JSRE struct {
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
type jsTimer struct {
timer *time.Timer
@ -68,17 +73,17 @@ type evalReq struct {
}
// 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{
assetPath: assetPath,
output: output,
closed: make(chan struct{}),
evalQueue: make(chan *evalReq),
stopEventLoop: make(chan bool),
vm: vm,
vm: goja.New(),
}
go re.runEventLoop()
re.Set("loadScript", re.loadScript)
re.Set("loadScript", MakeCallback(re.vm, re.loadScript))
re.Set("inspect", re.prettyPrintJS)
return re
}
@ -244,21 +249,7 @@ func (re *JSRE) Exec(file string) error {
if err != nil {
return err
}
var script *goja.Program
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)
return re.Compile(file, string(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
}
// 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.
func (re *JSRE) Set(ns string, v interface{}) (err error) {
re.Do(func(vm *goja.Runtime) { vm.Set(ns, v) })
return err
}
// loadScript executes a JS script from inside the currently executing JS code.
func (re *JSRE) loadScript(call goja.FunctionCall) goja.Value {
file := call.Argument(0).ToString().String()
file = common.AbsolutePath(re.assetPath, file)
source, err := ioutil.ReadFile(file)
if err != nil {
// Panicking with a goja.Value arg will cause a JS exception
// in the caller.
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
// MakeCallback turns the given function into a function that's callable by JS.
func MakeCallback(vm *goja.Runtime, fn func(Call) (goja.Value, error)) goja.Value {
return vm.ToValue(func(call goja.FunctionCall) goja.Value {
result, err := fn(Call{call, vm})
if err != nil {
panic(vm.NewGoError(err))
}
return result
})
}
// Evaluate executes code and pretty prints the result to the specified output
// stream.
func (re *JSRE) Evaluate(code string, w io.Writer) error {
var fail error
// Evaluate executes code and pretty prints the result to the specified output stream.
func (re *JSRE) Evaluate(code string, w io.Writer) {
re.Do(func(vm *goja.Runtime) {
val, err := vm.RunString(code)
if err != nil {
@ -310,7 +286,6 @@ func (re *JSRE) Evaluate(code string, w io.Writer) error {
}
fmt.Fprintln(w)
})
return fail
}
// 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
}
// 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) {
script, err := goja.Compile(filename, src, false)
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)
}
}
jsre := New(dir, os.Stdout, goja.New())
jsre := New(dir, os.Stdout)
return jsre, dir
}
@ -102,10 +102,10 @@ func TestNatto(t *testing.T) {
}
func TestBind(t *testing.T) {
jsre := New("", os.Stdout, goja.New())
jsre := New("", os.Stdout)
defer jsre.Stop(false)
jsre.Bind("no", &testNativeObjectBinding{vm: jsre.vm})
jsre.Set("no", &testNativeObjectBinding{vm: jsre.vm})
_, err := jsre.Run(`no.TestMethod("testMsg")`)
if err != nil {