github.com/jeffallen/go-ethereum@v1.1.4-0.20150910155051-571d3236c49c/jsre/jsre.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package jsre provides execution environment for JavaScript.
    18  package jsre
    19  
    20  import (
    21  	"fmt"
    22  	"io/ioutil"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/robertkrimen/otto"
    28  )
    29  
    30  /*
    31  JSRE is a generic JS runtime environment embedding the otto JS interpreter.
    32  It provides some helper functions to
    33  - load code from files
    34  - run code snippets
    35  - require libraries
    36  - bind native go objects
    37  */
    38  type JSRE struct {
    39  	assetPath     string
    40  	evalQueue     chan *evalReq
    41  	stopEventLoop chan bool
    42  	loopWg        sync.WaitGroup
    43  }
    44  
    45  // jsTimer is a single timer instance with a callback function
    46  type jsTimer struct {
    47  	timer    *time.Timer
    48  	duration time.Duration
    49  	interval bool
    50  	call     otto.FunctionCall
    51  }
    52  
    53  // evalReq is a serialized vm execution request processed by runEventLoop.
    54  type evalReq struct {
    55  	fn   func(vm *otto.Otto)
    56  	done chan bool
    57  }
    58  
    59  // runtime must be stopped with Stop() after use and cannot be used after stopping
    60  func New(assetPath string) *JSRE {
    61  	re := &JSRE{
    62  		assetPath:     assetPath,
    63  		evalQueue:     make(chan *evalReq),
    64  		stopEventLoop: make(chan bool),
    65  	}
    66  	re.loopWg.Add(1)
    67  	go re.runEventLoop()
    68  	re.Set("loadScript", re.loadScript)
    69  	re.Set("inspect", prettyPrintJS)
    70  	return re
    71  }
    72  
    73  // This function runs the main event loop from a goroutine that is started
    74  // when JSRE is created. Use Stop() before exiting to properly stop it.
    75  // The event loop processes vm access requests from the evalQueue in a
    76  // serialized way and calls timer callback functions at the appropriate time.
    77  
    78  // Exported functions always access the vm through the event queue. You can
    79  // call the functions of the otto vm directly to circumvent the queue. These
    80  // functions should be used if and only if running a routine that was already
    81  // called from JS through an RPC call.
    82  func (self *JSRE) runEventLoop() {
    83  	vm := otto.New()
    84  	registry := map[*jsTimer]*jsTimer{}
    85  	ready := make(chan *jsTimer)
    86  
    87  	newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
    88  
    89  		delay, _ := call.Argument(1).ToInteger()
    90  		if 0 >= delay {
    91  			delay = 1
    92  		}
    93  		timer := &jsTimer{
    94  			duration: time.Duration(delay) * time.Millisecond,
    95  			call:     call,
    96  			interval: interval,
    97  		}
    98  		registry[timer] = timer
    99  
   100  		timer.timer = time.AfterFunc(timer.duration, func() {
   101  			ready <- timer
   102  		})
   103  
   104  		value, err := call.Otto.ToValue(timer)
   105  		if err != nil {
   106  			panic(err)
   107  		}
   108  
   109  		return timer, value
   110  	}
   111  
   112  	setTimeout := func(call otto.FunctionCall) otto.Value {
   113  		_, value := newTimer(call, false)
   114  		return value
   115  	}
   116  
   117  	setInterval := func(call otto.FunctionCall) otto.Value {
   118  		_, value := newTimer(call, true)
   119  		return value
   120  	}
   121  
   122  	clearTimeout := func(call otto.FunctionCall) otto.Value {
   123  		timer, _ := call.Argument(0).Export()
   124  		if timer, ok := timer.(*jsTimer); ok {
   125  			timer.timer.Stop()
   126  			delete(registry, timer)
   127  		}
   128  		return otto.UndefinedValue()
   129  	}
   130  	vm.Set("setTimeout", setTimeout)
   131  	vm.Set("setInterval", setInterval)
   132  	vm.Set("clearTimeout", clearTimeout)
   133  	vm.Set("clearInterval", clearTimeout)
   134  
   135  	var waitForCallbacks bool
   136  
   137  loop:
   138  	for {
   139  		select {
   140  		case timer := <-ready:
   141  			// execute callback, remove/reschedule the timer
   142  			var arguments []interface{}
   143  			if len(timer.call.ArgumentList) > 2 {
   144  				tmp := timer.call.ArgumentList[2:]
   145  				arguments = make([]interface{}, 2+len(tmp))
   146  				for i, value := range tmp {
   147  					arguments[i+2] = value
   148  				}
   149  			} else {
   150  				arguments = make([]interface{}, 1)
   151  			}
   152  			arguments[0] = timer.call.ArgumentList[0]
   153  			_, err := vm.Call(`Function.call.call`, nil, arguments...)
   154  			if err != nil {
   155  				fmt.Println("js error:", err, arguments)
   156  			}
   157  			if timer.interval {
   158  				timer.timer.Reset(timer.duration)
   159  			} else {
   160  				delete(registry, timer)
   161  				if waitForCallbacks && (len(registry) == 0) {
   162  					break loop
   163  				}
   164  			}
   165  		case req := <-self.evalQueue:
   166  			// run the code, send the result back
   167  			req.fn(vm)
   168  			close(req.done)
   169  			if waitForCallbacks && (len(registry) == 0) {
   170  				break loop
   171  			}
   172  		case waitForCallbacks = <-self.stopEventLoop:
   173  			if !waitForCallbacks || (len(registry) == 0) {
   174  				break loop
   175  			}
   176  		}
   177  	}
   178  
   179  	for _, timer := range registry {
   180  		timer.timer.Stop()
   181  		delete(registry, timer)
   182  	}
   183  
   184  	self.loopWg.Done()
   185  }
   186  
   187  // do schedules the given function on the event loop.
   188  func (self *JSRE) do(fn func(*otto.Otto)) {
   189  	done := make(chan bool)
   190  	req := &evalReq{fn, done}
   191  	self.evalQueue <- req
   192  	<-done
   193  }
   194  
   195  // stops the event loop before exit, optionally waits for all timers to expire
   196  func (self *JSRE) Stop(waitForCallbacks bool) {
   197  	self.stopEventLoop <- waitForCallbacks
   198  	self.loopWg.Wait()
   199  }
   200  
   201  // Exec(file) loads and runs the contents of a file
   202  // if a relative path is given, the jsre's assetPath is used
   203  func (self *JSRE) Exec(file string) error {
   204  	code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file))
   205  	if err != nil {
   206  		return err
   207  	}
   208  	self.do(func(vm *otto.Otto) { _, err = vm.Run(code) })
   209  	return err
   210  }
   211  
   212  // Bind assigns value v to a variable in the JS environment
   213  // This method is deprecated, use Set.
   214  func (self *JSRE) Bind(name string, v interface{}) error {
   215  	return self.Set(name, v)
   216  }
   217  
   218  // Run runs a piece of JS code.
   219  func (self *JSRE) Run(code string) (v otto.Value, err error) {
   220  	self.do(func(vm *otto.Otto) { v, err = vm.Run(code) })
   221  	return v, err
   222  }
   223  
   224  // Get returns the value of a variable in the JS environment.
   225  func (self *JSRE) Get(ns string) (v otto.Value, err error) {
   226  	self.do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
   227  	return v, err
   228  }
   229  
   230  // Set assigns value v to a variable in the JS environment.
   231  func (self *JSRE) Set(ns string, v interface{}) (err error) {
   232  	self.do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
   233  	return err
   234  }
   235  
   236  // loadScript executes a JS script from inside the currently executing JS code.
   237  func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
   238  	file, err := call.Argument(0).ToString()
   239  	if err != nil {
   240  		// TODO: throw exception
   241  		return otto.FalseValue()
   242  	}
   243  	file = common.AbsolutePath(self.assetPath, file)
   244  	source, err := ioutil.ReadFile(file)
   245  	if err != nil {
   246  		// TODO: throw exception
   247  		return otto.FalseValue()
   248  	}
   249  	if _, err := compileAndRun(call.Otto, file, source); err != nil {
   250  		// TODO: throw exception
   251  		fmt.Println("err:", err)
   252  		return otto.FalseValue()
   253  	}
   254  	// TODO: return evaluation result
   255  	return otto.TrueValue()
   256  }
   257  
   258  // EvalAndPrettyPrint evaluates code and pretty prints the result to
   259  // standard output.
   260  func (self *JSRE) EvalAndPrettyPrint(code string) (err error) {
   261  	self.do(func(vm *otto.Otto) {
   262  		var val otto.Value
   263  		val, err = vm.Run(code)
   264  		if err != nil {
   265  			return
   266  		}
   267  		prettyPrint(vm, val)
   268  		fmt.Println()
   269  	})
   270  	return err
   271  }
   272  
   273  // Compile compiles and then runs a piece of JS code.
   274  func (self *JSRE) Compile(filename string, src interface{}) (err error) {
   275  	self.do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
   276  	return err
   277  }
   278  
   279  func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
   280  	script, err := vm.Compile(filename, src)
   281  	if err != nil {
   282  		return otto.Value{}, err
   283  	}
   284  	return vm.Run(script)
   285  }