| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | package jsre | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2015-03-24 17:49:28 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-16 22:48:54 +07:00
										 |  |  | 	"github.com/ethereum/go-ethereum/common" | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 	"github.com/robertkrimen/otto" | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | JSRE is a generic JS runtime environment embedding the otto JS interpreter. | 
					
						
							|  |  |  | It provides some helper functions to | 
					
						
							|  |  |  | - load code from files | 
					
						
							|  |  |  | - run code snippets | 
					
						
							|  |  |  | - require libraries | 
					
						
							|  |  |  | - bind native go objects | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | type JSRE struct { | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	assetPath     string | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 	evalQueue     chan *evalReq | 
					
						
							|  |  |  | 	stopEventLoop chan bool | 
					
						
							|  |  |  | 	loopWg        sync.WaitGroup | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // jsTimer is a single timer instance with a callback function | 
					
						
							|  |  |  | type jsTimer struct { | 
					
						
							|  |  |  | 	timer    *time.Timer | 
					
						
							|  |  |  | 	duration time.Duration | 
					
						
							|  |  |  | 	interval bool | 
					
						
							|  |  |  | 	call     otto.FunctionCall | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // evalReq is a serialized vm execution request processed by runEventLoop. | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | type evalReq struct { | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	fn   func(vm *otto.Otto) | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 	done chan bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // runtime must be stopped with Stop() after use and cannot be used after stopping | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | func New(assetPath string) *JSRE { | 
					
						
							|  |  |  | 	re := &JSRE{ | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 		assetPath:     assetPath, | 
					
						
							|  |  |  | 		evalQueue:     make(chan *evalReq), | 
					
						
							|  |  |  | 		stopEventLoop: make(chan bool), | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 	re.loopWg.Add(1) | 
					
						
							|  |  |  | 	go re.runEventLoop() | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	re.Compile("pp.js", pp_js) // load prettyprint func definition | 
					
						
							|  |  |  | 	re.Set("loadScript", re.loadScript) | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | 	return re | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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. | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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. | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | func (self *JSRE) runEventLoop() { | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	vm := otto.New() | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 	registry := map[*jsTimer]*jsTimer{} | 
					
						
							|  |  |  | 	ready := make(chan *jsTimer) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		delay, _ := call.Argument(1).ToInteger() | 
					
						
							|  |  |  | 		if 0 >= delay { | 
					
						
							|  |  |  | 			delay = 1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		timer := &jsTimer{ | 
					
						
							|  |  |  | 			duration: time.Duration(delay) * time.Millisecond, | 
					
						
							|  |  |  | 			call:     call, | 
					
						
							|  |  |  | 			interval: interval, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		registry[timer] = timer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		timer.timer = time.AfterFunc(timer.duration, func() { | 
					
						
							|  |  |  | 			ready <- timer | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		value, err := call.Otto.ToValue(timer) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			panic(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return timer, value | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	setTimeout := func(call otto.FunctionCall) otto.Value { | 
					
						
							|  |  |  | 		_, value := newTimer(call, false) | 
					
						
							|  |  |  | 		return value | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	setInterval := func(call otto.FunctionCall) otto.Value { | 
					
						
							|  |  |  | 		_, value := newTimer(call, true) | 
					
						
							|  |  |  | 		return value | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clearTimeout := func(call otto.FunctionCall) otto.Value { | 
					
						
							|  |  |  | 		timer, _ := call.Argument(0).Export() | 
					
						
							|  |  |  | 		if timer, ok := timer.(*jsTimer); ok { | 
					
						
							|  |  |  | 			timer.timer.Stop() | 
					
						
							|  |  |  | 			delete(registry, timer) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return otto.UndefinedValue() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	vm.Set("setTimeout", setTimeout) | 
					
						
							|  |  |  | 	vm.Set("setInterval", setInterval) | 
					
						
							|  |  |  | 	vm.Set("clearTimeout", clearTimeout) | 
					
						
							|  |  |  | 	vm.Set("clearInterval", clearTimeout) | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var waitForCallbacks bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | loop: | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case timer := <-ready: | 
					
						
							|  |  |  | 			// execute callback, remove/reschedule the timer | 
					
						
							|  |  |  | 			var arguments []interface{} | 
					
						
							|  |  |  | 			if len(timer.call.ArgumentList) > 2 { | 
					
						
							|  |  |  | 				tmp := timer.call.ArgumentList[2:] | 
					
						
							|  |  |  | 				arguments = make([]interface{}, 2+len(tmp)) | 
					
						
							|  |  |  | 				for i, value := range tmp { | 
					
						
							|  |  |  | 					arguments[i+2] = value | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				arguments = make([]interface{}, 1) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			arguments[0] = timer.call.ArgumentList[0] | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 			_, err := vm.Call(`Function.call.call`, nil, arguments...) | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2015-05-07 19:56:44 +02:00
										 |  |  | 				fmt.Println("js error:", err, arguments) | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			if timer.interval { | 
					
						
							|  |  |  | 				timer.timer.Reset(timer.duration) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				delete(registry, timer) | 
					
						
							|  |  |  | 				if waitForCallbacks && (len(registry) == 0) { | 
					
						
							|  |  |  | 					break loop | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 		case req := <-self.evalQueue: | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 			// run the code, send the result back | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 			req.fn(vm) | 
					
						
							|  |  |  | 			close(req.done) | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | 			if waitForCallbacks && (len(registry) == 0) { | 
					
						
							|  |  |  | 				break loop | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case waitForCallbacks = <-self.stopEventLoop: | 
					
						
							|  |  |  | 			if !waitForCallbacks || (len(registry) == 0) { | 
					
						
							|  |  |  | 				break loop | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, timer := range registry { | 
					
						
							|  |  |  | 		timer.timer.Stop() | 
					
						
							|  |  |  | 		delete(registry, timer) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	self.loopWg.Done() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-22 02:31:59 +02:00
										 |  |  | // stops the event loop before exit, optionally waits for all timers to expire | 
					
						
							|  |  |  | func (self *JSRE) Stop(waitForCallbacks bool) { | 
					
						
							|  |  |  | 	self.stopEventLoop <- waitForCallbacks | 
					
						
							|  |  |  | 	self.loopWg.Wait() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | // Exec(file) loads and runs the contents of a file | 
					
						
							|  |  |  | // if a relative path is given, the jsre's assetPath is used | 
					
						
							|  |  |  | func (self *JSRE) Exec(file string) error { | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file)) | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	self.do(func(vm *otto.Otto) { _, err = vm.Run(code) }) | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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) | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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 | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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 | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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 | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // loadScript executes a JS script from inside the currently executing JS code. | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value { | 
					
						
							|  |  |  | 	file, err := call.Argument(0).ToString() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 		// TODO: throw exception | 
					
						
							|  |  |  | 		return otto.FalseValue() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	file = common.AbsolutePath(self.assetPath, file) | 
					
						
							|  |  |  | 	source, err := ioutil.ReadFile(file) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		// TODO: throw exception | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | 		return otto.FalseValue() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	if _, err := compileAndRun(call.Otto, file, source); err != nil { | 
					
						
							|  |  |  | 		// TODO: throw exception | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | 		fmt.Println("err:", err) | 
					
						
							|  |  |  | 		return otto.FalseValue() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	// TODO: return evaluation result | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | 	return otto.TrueValue() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // PrettyPrint writes v to standard output. | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) { | 
					
						
							|  |  |  | 	var method otto.Value | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	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 | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // Eval evaluates JS function and returns result in a pretty printed string format. | 
					
						
							| 
									
										
										
										
											2015-03-15 13:13:39 +07:00
										 |  |  | func (self *JSRE) Eval(code string) (s string, err error) { | 
					
						
							|  |  |  | 	var val otto.Value | 
					
						
							|  |  |  | 	val, err = self.Run(code) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	val, err = self.PrettyPrint(val) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return fmt.Sprintf("%v", val), nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-03-24 17:49:28 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | // 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) | 
					
						
							| 
									
										
										
										
											2015-03-24 17:49:28 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 		return otto.Value{}, err | 
					
						
							| 
									
										
										
										
											2015-03-24 17:49:28 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-05-25 02:27:37 +02:00
										 |  |  | 	return vm.Run(script) | 
					
						
							| 
									
										
										
										
											2015-03-24 17:49:28 +01:00
										 |  |  | } |