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 }