|
@@ -19,9 +19,7 @@ It provides some helper functions to
|
|
|
- bind native go objects
|
|
- bind native go objects
|
|
|
*/
|
|
*/
|
|
|
type JSRE struct {
|
|
type JSRE struct {
|
|
|
- assetPath string
|
|
|
|
|
- vm *otto.Otto
|
|
|
|
|
-
|
|
|
|
|
|
|
+ assetPath string
|
|
|
evalQueue chan *evalReq
|
|
evalQueue chan *evalReq
|
|
|
stopEventLoop chan bool
|
|
stopEventLoop chan bool
|
|
|
loopWg sync.WaitGroup
|
|
loopWg sync.WaitGroup
|
|
@@ -35,68 +33,37 @@ type jsTimer struct {
|
|
|
call otto.FunctionCall
|
|
call otto.FunctionCall
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// evalResult is a structure to store the result of any serialized vm execution
|
|
|
|
|
-type evalResult struct {
|
|
|
|
|
- result otto.Value
|
|
|
|
|
- err error
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// evalReq is a serialized vm execution request put in evalQueue and processed by runEventLoop
|
|
|
|
|
|
|
+// evalReq is a serialized vm execution request processed by runEventLoop.
|
|
|
type evalReq struct {
|
|
type evalReq struct {
|
|
|
- fn func(res *evalResult)
|
|
|
|
|
|
|
+ fn func(vm *otto.Otto)
|
|
|
done chan bool
|
|
done chan bool
|
|
|
- res evalResult
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 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) *JSRE {
|
|
func New(assetPath string) *JSRE {
|
|
|
re := &JSRE{
|
|
re := &JSRE{
|
|
|
- assetPath: assetPath,
|
|
|
|
|
- vm: otto.New(),
|
|
|
|
|
|
|
+ assetPath: assetPath,
|
|
|
|
|
+ evalQueue: make(chan *evalReq),
|
|
|
|
|
+ stopEventLoop: make(chan bool),
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // load prettyprint func definition
|
|
|
|
|
- re.vm.Run(pp_js)
|
|
|
|
|
- re.vm.Set("loadScript", re.loadScript)
|
|
|
|
|
-
|
|
|
|
|
- re.evalQueue = make(chan *evalReq)
|
|
|
|
|
- re.stopEventLoop = make(chan bool)
|
|
|
|
|
re.loopWg.Add(1)
|
|
re.loopWg.Add(1)
|
|
|
go re.runEventLoop()
|
|
go re.runEventLoop()
|
|
|
-
|
|
|
|
|
|
|
+ re.Compile("pp.js", pp_js) // load prettyprint func definition
|
|
|
|
|
+ re.Set("loadScript", re.loadScript)
|
|
|
return re
|
|
return re
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// this function runs a piece of JS code either in a serialized way (when useEQ is true) or instantly, circumventing the evalQueue
|
|
|
|
|
-func (self *JSRE) run(src interface{}, useEQ bool) (value otto.Value, err error) {
|
|
|
|
|
- if useEQ {
|
|
|
|
|
- done := make(chan bool)
|
|
|
|
|
- req := &evalReq{
|
|
|
|
|
- fn: func(res *evalResult) {
|
|
|
|
|
- res.result, res.err = self.vm.Run(src)
|
|
|
|
|
- },
|
|
|
|
|
- done: done,
|
|
|
|
|
- }
|
|
|
|
|
- self.evalQueue <- req
|
|
|
|
|
- <-done
|
|
|
|
|
- return req.res.result, req.res.err
|
|
|
|
|
- } else {
|
|
|
|
|
- return self.vm.Run(src)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+// This function runs the main event loop from a goroutine that is started
|
|
|
|
|
+// when JSRE is created. Use Stop() before exiting to properly stop it.
|
|
|
|
|
+// The event loop processes vm access requests from the evalQueue in a
|
|
|
|
|
+// serialized way and calls timer callback functions at the appropriate time.
|
|
|
|
|
|
|
|
-/*
|
|
|
|
|
-This function runs the main event loop from a goroutine that is started
|
|
|
|
|
- when JSRE is created. Use Stop() before exiting to properly stop it.
|
|
|
|
|
-The event loop processes vm access requests from the evalQueue in a
|
|
|
|
|
- serialized way and calls timer callback functions at the appropriate time.
|
|
|
|
|
-
|
|
|
|
|
-Exported functions always access the vm through the event queue. You can
|
|
|
|
|
- call the functions of the otto vm directly to circumvent the queue. These
|
|
|
|
|
- functions should be used if and only if running a routine that was already
|
|
|
|
|
- called from JS through an RPC call.
|
|
|
|
|
-*/
|
|
|
|
|
|
|
+// Exported functions always access the vm through the event queue. You can
|
|
|
|
|
+// call the functions of the otto vm directly to circumvent the queue. These
|
|
|
|
|
+// functions should be used if and only if running a routine that was already
|
|
|
|
|
+// called from JS through an RPC call.
|
|
|
func (self *JSRE) runEventLoop() {
|
|
func (self *JSRE) runEventLoop() {
|
|
|
|
|
+ vm := otto.New()
|
|
|
registry := map[*jsTimer]*jsTimer{}
|
|
registry := map[*jsTimer]*jsTimer{}
|
|
|
ready := make(chan *jsTimer)
|
|
ready := make(chan *jsTimer)
|
|
|
|
|
|
|
@@ -143,10 +110,10 @@ func (self *JSRE) runEventLoop() {
|
|
|
}
|
|
}
|
|
|
return otto.UndefinedValue()
|
|
return otto.UndefinedValue()
|
|
|
}
|
|
}
|
|
|
- self.vm.Set("setTimeout", setTimeout)
|
|
|
|
|
- self.vm.Set("setInterval", setInterval)
|
|
|
|
|
- self.vm.Set("clearTimeout", clearTimeout)
|
|
|
|
|
- self.vm.Set("clearInterval", clearTimeout)
|
|
|
|
|
|
|
+ vm.Set("setTimeout", setTimeout)
|
|
|
|
|
+ vm.Set("setInterval", setInterval)
|
|
|
|
|
+ vm.Set("clearTimeout", clearTimeout)
|
|
|
|
|
+ vm.Set("clearInterval", clearTimeout)
|
|
|
|
|
|
|
|
var waitForCallbacks bool
|
|
var waitForCallbacks bool
|
|
|
|
|
|
|
@@ -166,8 +133,7 @@ loop:
|
|
|
arguments = make([]interface{}, 1)
|
|
arguments = make([]interface{}, 1)
|
|
|
}
|
|
}
|
|
|
arguments[0] = timer.call.ArgumentList[0]
|
|
arguments[0] = timer.call.ArgumentList[0]
|
|
|
- _, err := self.vm.Call(`Function.call.call`, nil, arguments...)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ _, err := vm.Call(`Function.call.call`, nil, arguments...)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
fmt.Println("js error:", err, arguments)
|
|
fmt.Println("js error:", err, arguments)
|
|
|
}
|
|
}
|
|
@@ -179,10 +145,10 @@ loop:
|
|
|
break loop
|
|
break loop
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- case evalReq := <-self.evalQueue:
|
|
|
|
|
|
|
+ case req := <-self.evalQueue:
|
|
|
// run the code, send the result back
|
|
// run the code, send the result back
|
|
|
- evalReq.fn(&evalReq.res)
|
|
|
|
|
- close(evalReq.done)
|
|
|
|
|
|
|
+ req.fn(vm)
|
|
|
|
|
+ close(req.done)
|
|
|
if waitForCallbacks && (len(registry) == 0) {
|
|
if waitForCallbacks && (len(registry) == 0) {
|
|
|
break loop
|
|
break loop
|
|
|
}
|
|
}
|
|
@@ -201,6 +167,14 @@ loop:
|
|
|
self.loopWg.Done()
|
|
self.loopWg.Done()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// do schedules the given function on the event loop.
|
|
|
|
|
+func (self *JSRE) do(fn func(*otto.Otto)) {
|
|
|
|
|
+ done := make(chan bool)
|
|
|
|
|
+ req := &evalReq{fn, done}
|
|
|
|
|
+ self.evalQueue <- req
|
|
|
|
|
+ <-done
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// stops the event loop before exit, optionally waits for all timers to expire
|
|
// stops the event loop before exit, optionally waits for all timers to expire
|
|
|
func (self *JSRE) Stop(waitForCallbacks bool) {
|
|
func (self *JSRE) Stop(waitForCallbacks bool) {
|
|
|
self.stopEventLoop <- waitForCallbacks
|
|
self.stopEventLoop <- waitForCallbacks
|
|
@@ -210,119 +184,78 @@ func (self *JSRE) Stop(waitForCallbacks bool) {
|
|
|
// Exec(file) loads and runs the contents of a file
|
|
// Exec(file) loads and runs the contents of a file
|
|
|
// if a relative path is given, the jsre's assetPath is used
|
|
// if a relative path is given, the jsre's assetPath is used
|
|
|
func (self *JSRE) Exec(file string) error {
|
|
func (self *JSRE) Exec(file string) error {
|
|
|
- return self.exec(common.AbsolutePath(self.assetPath, file), true)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// circumvents the eval queue, see runEventLoop
|
|
|
|
|
-func (self *JSRE) execWithoutEQ(file string) error {
|
|
|
|
|
- return self.exec(common.AbsolutePath(self.assetPath, file), false)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (self *JSRE) exec(path string, useEQ bool) error {
|
|
|
|
|
- code, err := ioutil.ReadFile(path)
|
|
|
|
|
|
|
+ code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file))
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
- _, err = self.run(code, useEQ)
|
|
|
|
|
|
|
+ self.do(func(vm *otto.Otto) { _, err = vm.Run(code) })
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// assigns value v to a variable in the JS environment
|
|
|
|
|
-func (self *JSRE) Bind(name string, v interface{}) (err error) {
|
|
|
|
|
- self.Set(name, v)
|
|
|
|
|
- return
|
|
|
|
|
|
|
+// Bind assigns value v to a variable in the JS environment
|
|
|
|
|
+// This method is deprecated, use Set.
|
|
|
|
|
+func (self *JSRE) Bind(name string, v interface{}) error {
|
|
|
|
|
+ return self.Set(name, v)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// runs a piece of JS code
|
|
|
|
|
-func (self *JSRE) Run(code string) (otto.Value, error) {
|
|
|
|
|
- return self.run(code, true)
|
|
|
|
|
|
|
+// Run runs a piece of JS code.
|
|
|
|
|
+func (self *JSRE) Run(code string) (v otto.Value, err error) {
|
|
|
|
|
+ self.do(func(vm *otto.Otto) { v, err = vm.Run(code) })
|
|
|
|
|
+ return v, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// returns the value of a variable in the JS environment
|
|
|
|
|
-func (self *JSRE) Get(ns string) (otto.Value, error) {
|
|
|
|
|
- done := make(chan bool)
|
|
|
|
|
- req := &evalReq{
|
|
|
|
|
- fn: func(res *evalResult) {
|
|
|
|
|
- res.result, res.err = self.vm.Get(ns)
|
|
|
|
|
- },
|
|
|
|
|
- done: done,
|
|
|
|
|
- }
|
|
|
|
|
- self.evalQueue <- req
|
|
|
|
|
- <-done
|
|
|
|
|
- return req.res.result, req.res.err
|
|
|
|
|
|
|
+// Get returns the value of a variable in the JS environment.
|
|
|
|
|
+func (self *JSRE) Get(ns string) (v otto.Value, err error) {
|
|
|
|
|
+ self.do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
|
|
|
|
|
+ return v, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// assigns value v to a variable in the JS environment
|
|
|
|
|
-func (self *JSRE) Set(ns string, v interface{}) error {
|
|
|
|
|
- done := make(chan bool)
|
|
|
|
|
- req := &evalReq{
|
|
|
|
|
- fn: func(res *evalResult) {
|
|
|
|
|
- res.err = self.vm.Set(ns, v)
|
|
|
|
|
- },
|
|
|
|
|
- done: done,
|
|
|
|
|
- }
|
|
|
|
|
- self.evalQueue <- req
|
|
|
|
|
- <-done
|
|
|
|
|
- return req.res.err
|
|
|
|
|
|
|
+// Set assigns value v to a variable in the JS environment.
|
|
|
|
|
+func (self *JSRE) Set(ns string, v interface{}) (err error) {
|
|
|
|
|
+ self.do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/*
|
|
|
|
|
-Executes a JS script from inside the currently executing JS code.
|
|
|
|
|
-Should only be called from inside an RPC routine.
|
|
|
|
|
-*/
|
|
|
|
|
|
|
+// loadScript executes a JS script from inside the currently executing JS code.
|
|
|
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
|
|
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
|
|
|
file, err := call.Argument(0).ToString()
|
|
file, err := call.Argument(0).ToString()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
|
|
+ // TODO: throw exception
|
|
|
|
|
+ return otto.FalseValue()
|
|
|
|
|
+ }
|
|
|
|
|
+ file = common.AbsolutePath(self.assetPath, file)
|
|
|
|
|
+ source, err := ioutil.ReadFile(file)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ // TODO: throw exception
|
|
|
return otto.FalseValue()
|
|
return otto.FalseValue()
|
|
|
}
|
|
}
|
|
|
- if err := self.execWithoutEQ(file); err != nil { // loadScript is only called from inside js
|
|
|
|
|
|
|
+ if _, err := compileAndRun(call.Otto, file, source); err != nil {
|
|
|
|
|
+ // TODO: throw exception
|
|
|
fmt.Println("err:", err)
|
|
fmt.Println("err:", err)
|
|
|
return otto.FalseValue()
|
|
return otto.FalseValue()
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+ // TODO: return evaluation result
|
|
|
return otto.TrueValue()
|
|
return otto.TrueValue()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// uses the "prettyPrint" JS function to format a value
|
|
|
|
|
|
|
+// PrettyPrint writes v to standard output.
|
|
|
func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
|
|
func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
|
|
|
var method otto.Value
|
|
var method otto.Value
|
|
|
- v, err = self.ToValue(v)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- method, err = self.vm.Get("prettyPrint")
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- return method.Call(method, v)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// creates an otto value from a go type (serialized version)
|
|
|
|
|
-func (self *JSRE) ToValue(v interface{}) (otto.Value, error) {
|
|
|
|
|
- done := make(chan bool)
|
|
|
|
|
- req := &evalReq{
|
|
|
|
|
- fn: func(res *evalResult) {
|
|
|
|
|
- res.result, res.err = self.vm.ToValue(v)
|
|
|
|
|
- },
|
|
|
|
|
- done: done,
|
|
|
|
|
- }
|
|
|
|
|
- self.evalQueue <- req
|
|
|
|
|
- <-done
|
|
|
|
|
- return req.res.result, req.res.err
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// creates an otto value from a go type (non-serialized version)
|
|
|
|
|
-func (self *JSRE) ToVal(v interface{}) otto.Value {
|
|
|
|
|
-
|
|
|
|
|
- result, err := self.vm.ToValue(v)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- fmt.Println("Value unknown:", err)
|
|
|
|
|
- return otto.UndefinedValue()
|
|
|
|
|
- }
|
|
|
|
|
- return result
|
|
|
|
|
|
|
+ self.do(func(vm *otto.Otto) {
|
|
|
|
|
+ val, err = vm.ToValue(v)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ method, err = vm.Get("prettyPrint")
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ val, err = method.Call(method, val)
|
|
|
|
|
+ })
|
|
|
|
|
+ return val, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// evaluates JS function and returns result in a pretty printed string format
|
|
|
|
|
|
|
+// Eval evaluates JS function and returns result in a pretty printed string format.
|
|
|
func (self *JSRE) Eval(code string) (s string, err error) {
|
|
func (self *JSRE) Eval(code string) (s string, err error) {
|
|
|
var val otto.Value
|
|
var val otto.Value
|
|
|
val, err = self.Run(code)
|
|
val, err = self.Run(code)
|
|
@@ -336,12 +269,16 @@ func (self *JSRE) Eval(code string) (s string, err error) {
|
|
|
return fmt.Sprintf("%v", val), nil
|
|
return fmt.Sprintf("%v", val), nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// compiles and then runs a piece of JS code
|
|
|
|
|
-func (self *JSRE) Compile(fn string, src interface{}) error {
|
|
|
|
|
- script, err := self.vm.Compile(fn, src)
|
|
|
|
|
|
|
+// Compile compiles and then runs a piece of JS code.
|
|
|
|
|
+func (self *JSRE) Compile(filename string, src interface{}) (err error) {
|
|
|
|
|
+ self.do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
|
|
|
|
|
+ return err
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
|
|
|
|
|
+ script, err := vm.Compile(filename, src)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return err
|
|
|
|
|
|
|
+ return otto.Value{}, err
|
|
|
}
|
|
}
|
|
|
- self.run(script, true)
|
|
|
|
|
- return nil
|
|
|
|
|
|
|
+ return vm.Run(script)
|
|
|
}
|
|
}
|