github.com/traefik/yaegi@v0.15.1/interp/interp.go (about) 1 package interp 2 3 import ( 4 "bufio" 5 "context" 6 "errors" 7 "fmt" 8 "go/build" 9 "go/scanner" 10 "go/token" 11 "io" 12 "io/fs" 13 "os" 14 "os/signal" 15 "path" 16 "path/filepath" 17 "reflect" 18 "runtime" 19 "runtime/debug" 20 "strconv" 21 "strings" 22 "sync" 23 "sync/atomic" 24 ) 25 26 // Interpreter node structure for AST and CFG. 27 type node struct { 28 debug *nodeDebugData // debug info 29 child []*node // child subtrees (AST) 30 anc *node // ancestor (AST) 31 param []*itype // generic parameter nodes (AST) 32 start *node // entry point in subtree (CFG) 33 tnext *node // true branch successor (CFG) 34 fnext *node // false branch successor (CFG) 35 interp *Interpreter // interpreter context 36 frame *frame // frame pointer used for closures only (TODO: suppress this) 37 index int64 // node index (dot display) 38 findex int // index of value in frame or frame size (func def, type def) 39 level int // number of frame indirections to access value 40 nleft int // number of children in left part (assign) or indicates preceding type (compositeLit) 41 nright int // number of children in right part (assign) 42 kind nkind // kind of node 43 pos token.Pos // position in source code, relative to fset 44 sym *symbol // associated symbol 45 typ *itype // type of value in frame, or nil 46 recv *receiver // method receiver node for call, or nil 47 types []reflect.Type // frame types, used by function literals only 48 scope *scope // frame scope 49 action action // action 50 exec bltn // generated function to execute 51 gen bltnGenerator // generator function to produce above bltn 52 val interface{} // static generic value (CFG execution) 53 rval reflect.Value // reflection value to let runtime access interpreter (CFG) 54 ident string // set if node is a var or func 55 redeclared bool // set if node is a redeclared variable (CFG) 56 meta interface{} // meta stores meta information between gta runs, like errors 57 } 58 59 func (n *node) shouldBreak() bool { 60 if n == nil || n.debug == nil { 61 return false 62 } 63 64 if n.debug.breakOnLine || n.debug.breakOnCall { 65 return true 66 } 67 68 return false 69 } 70 71 func (n *node) setProgram(p *Program) { 72 if n.debug == nil { 73 n.debug = new(nodeDebugData) 74 } 75 n.debug.program = p 76 } 77 78 func (n *node) setBreakOnCall(v bool) { 79 if n.debug == nil { 80 if !v { 81 return 82 } 83 n.debug = new(nodeDebugData) 84 } 85 n.debug.breakOnCall = v 86 } 87 88 func (n *node) setBreakOnLine(v bool) { 89 if n.debug == nil { 90 if !v { 91 return 92 } 93 n.debug = new(nodeDebugData) 94 } 95 n.debug.breakOnLine = v 96 } 97 98 // receiver stores method receiver object access path. 99 type receiver struct { 100 node *node // receiver value for alias and struct types 101 val reflect.Value // receiver value for interface type and value type 102 index []int // path in receiver value for interface or value type 103 } 104 105 // frame contains values for the current execution level (a function context). 106 type frame struct { 107 // id is an atomic counter used for cancellation, only accessed 108 // via newFrame/runid/setrunid/clone. 109 // Located at start of struct to ensure proper alignment. 110 id uint64 111 112 debug *frameDebugData 113 114 root *frame // global space 115 anc *frame // ancestor frame (caller space) 116 data []reflect.Value // values 117 118 mutex sync.RWMutex 119 deferred [][]reflect.Value // defer stack 120 recovered interface{} // to handle panic recover 121 done reflect.SelectCase // for cancellation of channel operations 122 } 123 124 func newFrame(anc *frame, length int, id uint64) *frame { 125 f := &frame{ 126 anc: anc, 127 data: make([]reflect.Value, length), 128 id: id, 129 } 130 if anc == nil { 131 f.root = f 132 } else { 133 f.done = anc.done 134 f.root = anc.root 135 } 136 return f 137 } 138 139 func (f *frame) runid() uint64 { return atomic.LoadUint64(&f.id) } 140 func (f *frame) setrunid(id uint64) { atomic.StoreUint64(&f.id, id) } 141 func (f *frame) clone(fork bool) *frame { 142 f.mutex.RLock() 143 defer f.mutex.RUnlock() 144 nf := &frame{ 145 anc: f.anc, 146 root: f.root, 147 deferred: f.deferred, 148 recovered: f.recovered, 149 id: f.runid(), 150 done: f.done, 151 debug: f.debug, 152 } 153 if fork { 154 nf.data = make([]reflect.Value, len(f.data)) 155 copy(nf.data, f.data) 156 } else { 157 nf.data = f.data 158 } 159 return nf 160 } 161 162 // Exports stores the map of binary packages per package path. 163 // The package path is the path joined from the import path and the package name 164 // as specified in source files by the "package" statement. 165 type Exports map[string]map[string]reflect.Value 166 167 // imports stores the map of source packages per package path. 168 type imports map[string]map[string]*symbol 169 170 // opt stores interpreter options. 171 type opt struct { 172 // dotCmd is the command to process the dot graph produced when astDot and/or 173 // cfgDot is enabled. It defaults to 'dot -Tdot -o <filename>.dot'. 174 dotCmd string 175 context build.Context // build context: GOPATH, build constraints 176 stdin io.Reader // standard input 177 stdout io.Writer // standard output 178 stderr io.Writer // standard error 179 args []string // cmdline args 180 env map[string]string // environment of interpreter, entries in form of "key=value" 181 filesystem fs.FS // filesystem containing sources 182 astDot bool // display AST graph (debug) 183 cfgDot bool // display CFG graph (debug) 184 noRun bool // compile, but do not run 185 fastChan bool // disable cancellable chan operations 186 specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors 187 unrestricted bool // allow use of non sandboxed symbols 188 } 189 190 // Interpreter contains global resources and state. 191 type Interpreter struct { 192 // id is an atomic counter counter used for run cancellation, 193 // only accessed via runid/stop 194 // Located at start of struct to ensure proper alignment on 32 bit 195 // architectures. 196 id uint64 197 198 // nindex is a node number incremented for each new node. 199 // It is used for debug (AST and CFG graphs). As it is atomically 200 // incremented, keep it aligned on 64 bits boundary. 201 nindex int64 202 203 name string // name of the input source file (or main) 204 205 opt // user settable options 206 cancelChan bool // enables cancellable chan operations 207 fset *token.FileSet // fileset to locate node in source code 208 binPkg Exports // binary packages used in interpreter, indexed by path 209 rdir map[string]bool // for src import cycle detection 210 mapTypes map[reflect.Value][]reflect.Type // special interfaces mapping for wrappers 211 212 mutex sync.RWMutex 213 frame *frame // program data storage during execution 214 universe *scope // interpreter global level scope 215 scopes map[string]*scope // package level scopes, indexed by import path 216 srcPkg imports // source packages used in interpreter, indexed by path 217 pkgNames map[string]string // package names, indexed by import path 218 done chan struct{} // for cancellation of channel operations 219 roots []*node 220 generic map[string]*node 221 222 hooks *hooks // symbol hooks 223 224 debugger *Debugger 225 } 226 227 const ( 228 mainID = "main" 229 selfPrefix = "github.com/traefik/yaegi" 230 selfPath = selfPrefix + "/interp/interp" 231 // DefaultSourceName is the name used by default when the name of the input 232 // source file has not been specified for an Eval. 233 // TODO(mpl): something even more special as a name? 234 DefaultSourceName = "_.go" 235 236 // Test is the value to pass to EvalPath to activate evaluation of test functions. 237 Test = false 238 // NoTest is the value to pass to EvalPath to skip evaluation of test functions. 239 NoTest = true 240 ) 241 242 // Self points to the current interpreter if accessed from within itself, or is nil. 243 var Self *Interpreter 244 245 // Symbols exposes interpreter values. 246 var Symbols = Exports{ 247 selfPath: map[string]reflect.Value{ 248 "New": reflect.ValueOf(New), 249 250 "Interpreter": reflect.ValueOf((*Interpreter)(nil)), 251 "Options": reflect.ValueOf((*Options)(nil)), 252 "Panic": reflect.ValueOf((*Panic)(nil)), 253 }, 254 } 255 256 func init() { Symbols[selfPath]["Symbols"] = reflect.ValueOf(Symbols) } 257 258 // _error is a wrapper of error interface type. 259 type _error struct { 260 IValue interface{} 261 WError func() string 262 } 263 264 func (w _error) Error() string { return w.WError() } 265 266 // Panic is an error recovered from a panic call in interpreted code. 267 type Panic struct { 268 // Value is the recovered value of a call to panic. 269 Value interface{} 270 271 // Callers is the call stack obtained from the recover call. 272 // It may be used as the parameter to runtime.CallersFrames. 273 Callers []uintptr 274 275 // Stack is the call stack buffer for debug. 276 Stack []byte 277 } 278 279 // TODO: Capture interpreter stack frames also and remove 280 // fmt.Fprintln(n.interp.stderr, oNode.cfgErrorf("panic")) in runCfg. 281 282 func (e Panic) Error() string { return fmt.Sprint(e.Value) } 283 284 // Walk traverses AST n in depth first order, call cbin function 285 // at node entry and cbout function at node exit. 286 func (n *node) Walk(in func(n *node) bool, out func(n *node)) { 287 if in != nil && !in(n) { 288 return 289 } 290 for _, child := range n.child { 291 child.Walk(in, out) 292 } 293 if out != nil { 294 out(n) 295 } 296 } 297 298 // Options are the interpreter options. 299 type Options struct { 300 // GoPath sets GOPATH for the interpreter. 301 GoPath string 302 303 // BuildTags sets build constraints for the interpreter. 304 BuildTags []string 305 306 // Standard input, output and error streams. 307 // They default to os.Stdin, os.Stdout and os.Stderr respectively. 308 Stdin io.Reader 309 Stdout, Stderr io.Writer 310 311 // Cmdline args, defaults to os.Args. 312 Args []string 313 314 // Environment of interpreter. Entries are in the form "key=values". 315 Env []string 316 317 // SourcecodeFilesystem is where the _sourcecode_ is loaded from and does 318 // NOT affect the filesystem of scripts when they run. 319 // It can be any fs.FS compliant filesystem (e.g. embed.FS, or fstest.MapFS for testing) 320 // See example/fs/fs_test.go for an example. 321 SourcecodeFilesystem fs.FS 322 323 // Unrestricted allows to run non sandboxed stdlib symbols such as os/exec and environment 324 Unrestricted bool 325 } 326 327 // New returns a new interpreter. 328 func New(options Options) *Interpreter { 329 i := Interpreter{ 330 opt: opt{context: build.Default, filesystem: &realFS{}, env: map[string]string{}}, 331 frame: newFrame(nil, 0, 0), 332 fset: token.NewFileSet(), 333 universe: initUniverse(), 334 scopes: map[string]*scope{}, 335 binPkg: Exports{"": map[string]reflect.Value{"_error": reflect.ValueOf((*_error)(nil))}}, 336 mapTypes: map[reflect.Value][]reflect.Type{}, 337 srcPkg: imports{}, 338 pkgNames: map[string]string{}, 339 rdir: map[string]bool{}, 340 hooks: &hooks{}, 341 generic: map[string]*node{}, 342 } 343 344 if i.opt.stdin = options.Stdin; i.opt.stdin == nil { 345 i.opt.stdin = os.Stdin 346 } 347 348 if i.opt.stdout = options.Stdout; i.opt.stdout == nil { 349 i.opt.stdout = os.Stdout 350 } 351 352 if i.opt.stderr = options.Stderr; i.opt.stderr == nil { 353 i.opt.stderr = os.Stderr 354 } 355 356 if i.opt.args = options.Args; i.opt.args == nil { 357 i.opt.args = os.Args 358 } 359 360 // unrestricted allows to use non sandboxed stdlib symbols and env. 361 if options.Unrestricted { 362 i.opt.unrestricted = true 363 } else { 364 for _, e := range options.Env { 365 a := strings.SplitN(e, "=", 2) 366 if len(a) == 2 { 367 i.opt.env[a[0]] = a[1] 368 } else { 369 i.opt.env[a[0]] = "" 370 } 371 } 372 } 373 374 if options.SourcecodeFilesystem != nil { 375 i.opt.filesystem = options.SourcecodeFilesystem 376 } 377 378 i.opt.context.GOPATH = options.GoPath 379 if len(options.BuildTags) > 0 { 380 i.opt.context.BuildTags = options.BuildTags 381 } 382 383 // astDot activates AST graph display for the interpreter 384 i.opt.astDot, _ = strconv.ParseBool(os.Getenv("YAEGI_AST_DOT")) 385 386 // cfgDot activates CFG graph display for the interpreter 387 i.opt.cfgDot, _ = strconv.ParseBool(os.Getenv("YAEGI_CFG_DOT")) 388 389 // dotCmd defines how to process the dot code generated whenever astDot and/or 390 // cfgDot is enabled. It defaults to 'dot -Tdot -o<filename>.dot' where filename 391 // is context dependent. 392 i.opt.dotCmd = os.Getenv("YAEGI_DOT_CMD") 393 394 // noRun disables the execution (but not the compilation) in the interpreter 395 i.opt.noRun, _ = strconv.ParseBool(os.Getenv("YAEGI_NO_RUN")) 396 397 // fastChan disables the cancellable version of channel operations in evalWithContext 398 i.opt.fastChan, _ = strconv.ParseBool(os.Getenv("YAEGI_FAST_CHAN")) 399 400 // specialStdio allows to assign directly io.Writer and io.Reader to os.Stdxxx, 401 // even if they are not file descriptors. 402 i.opt.specialStdio, _ = strconv.ParseBool(os.Getenv("YAEGI_SPECIAL_STDIO")) 403 404 return &i 405 } 406 407 const ( 408 bltnAppend = "append" 409 bltnCap = "cap" 410 bltnClose = "close" 411 bltnComplex = "complex" 412 bltnImag = "imag" 413 bltnCopy = "copy" 414 bltnDelete = "delete" 415 bltnLen = "len" 416 bltnMake = "make" 417 bltnNew = "new" 418 bltnPanic = "panic" 419 bltnPrint = "print" 420 bltnPrintln = "println" 421 bltnReal = "real" 422 bltnRecover = "recover" 423 ) 424 425 func initUniverse() *scope { 426 sc := &scope{global: true, sym: map[string]*symbol{ 427 // predefined Go types 428 "any": {kind: typeSym, typ: &itype{cat: interfaceT, str: "any"}}, 429 "bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool", str: "bool"}}, 430 "byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}}, 431 "comparable": {kind: typeSym, typ: &itype{cat: comparableT, name: "comparable", str: "comparable"}}, 432 "complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64", str: "complex64"}}, 433 "complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128", str: "complex128"}}, 434 "error": {kind: typeSym, typ: &itype{cat: errorT, name: "error", str: "error"}}, 435 "float32": {kind: typeSym, typ: &itype{cat: float32T, name: "float32", str: "float32"}}, 436 "float64": {kind: typeSym, typ: &itype{cat: float64T, name: "float64", str: "float64"}}, 437 "int": {kind: typeSym, typ: &itype{cat: intT, name: "int", str: "int"}}, 438 "int8": {kind: typeSym, typ: &itype{cat: int8T, name: "int8", str: "int8"}}, 439 "int16": {kind: typeSym, typ: &itype{cat: int16T, name: "int16", str: "int16"}}, 440 "int32": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}}, 441 "int64": {kind: typeSym, typ: &itype{cat: int64T, name: "int64", str: "int64"}}, 442 "interface{}": {kind: typeSym, typ: &itype{cat: interfaceT, str: "interface{}"}}, 443 "rune": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}}, 444 "string": {kind: typeSym, typ: &itype{cat: stringT, name: "string", str: "string"}}, 445 "uint": {kind: typeSym, typ: &itype{cat: uintT, name: "uint", str: "uint"}}, 446 "uint8": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}}, 447 "uint16": {kind: typeSym, typ: &itype{cat: uint16T, name: "uint16", str: "uint16"}}, 448 "uint32": {kind: typeSym, typ: &itype{cat: uint32T, name: "uint32", str: "uint32"}}, 449 "uint64": {kind: typeSym, typ: &itype{cat: uint64T, name: "uint64", str: "uint64"}}, 450 "uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr", str: "uintptr"}}, 451 452 // predefined Go constants 453 "false": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(false)}, 454 "true": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(true)}, 455 "iota": {kind: constSym, typ: untypedInt(nil)}, 456 457 // predefined Go zero value 458 "nil": {typ: &itype{cat: nilT, untyped: true, str: "nil"}}, 459 460 // predefined Go builtins 461 bltnAppend: {kind: bltnSym, builtin: _append}, 462 bltnCap: {kind: bltnSym, builtin: _cap}, 463 bltnClose: {kind: bltnSym, builtin: _close}, 464 bltnComplex: {kind: bltnSym, builtin: _complex}, 465 bltnImag: {kind: bltnSym, builtin: _imag}, 466 bltnCopy: {kind: bltnSym, builtin: _copy}, 467 bltnDelete: {kind: bltnSym, builtin: _delete}, 468 bltnLen: {kind: bltnSym, builtin: _len}, 469 bltnMake: {kind: bltnSym, builtin: _make}, 470 bltnNew: {kind: bltnSym, builtin: _new}, 471 bltnPanic: {kind: bltnSym, builtin: _panic}, 472 bltnPrint: {kind: bltnSym, builtin: _print}, 473 bltnPrintln: {kind: bltnSym, builtin: _println}, 474 bltnReal: {kind: bltnSym, builtin: _real}, 475 bltnRecover: {kind: bltnSym, builtin: _recover}, 476 }} 477 return sc 478 } 479 480 // resizeFrame resizes the global frame of interpreter. 481 func (interp *Interpreter) resizeFrame() { 482 l := len(interp.universe.types) 483 b := len(interp.frame.data) 484 if l-b <= 0 { 485 return 486 } 487 data := make([]reflect.Value, l) 488 copy(data, interp.frame.data) 489 for j, t := range interp.universe.types[b:] { 490 data[b+j] = reflect.New(t).Elem() 491 } 492 interp.frame.data = data 493 } 494 495 // Eval evaluates Go code represented as a string. Eval returns the last result 496 // computed by the interpreter, and a non nil error in case of failure. 497 func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) { 498 return interp.eval(src, "", true) 499 } 500 501 // EvalPath evaluates Go code located at path and returns the last result computed 502 // by the interpreter, and a non nil error in case of failure. 503 // The main function of the main package is executed if present. 504 func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error) { 505 if !isFile(interp.opt.filesystem, path) { 506 _, err := interp.importSrc(mainID, path, NoTest) 507 return res, err 508 } 509 510 b, err := fs.ReadFile(interp.filesystem, path) 511 if err != nil { 512 return res, err 513 } 514 return interp.eval(string(b), path, false) 515 } 516 517 // EvalPathWithContext evaluates Go code located at path and returns the last 518 // result computed by the interpreter, and a non nil error in case of failure. 519 // The main function of the main package is executed if present. 520 func (interp *Interpreter) EvalPathWithContext(ctx context.Context, path string) (res reflect.Value, err error) { 521 interp.mutex.Lock() 522 interp.done = make(chan struct{}) 523 interp.cancelChan = !interp.opt.fastChan 524 interp.mutex.Unlock() 525 526 done := make(chan struct{}) 527 go func() { 528 defer close(done) 529 res, err = interp.EvalPath(path) 530 }() 531 532 select { 533 case <-ctx.Done(): 534 interp.stop() 535 return reflect.Value{}, ctx.Err() 536 case <-done: 537 } 538 return res, err 539 } 540 541 // EvalTest evaluates Go code located at path, including test files with "_test.go" suffix. 542 // A non nil error is returned in case of failure. 543 // The main function, test functions and benchmark functions are internally compiled but not 544 // executed. Test functions can be retrieved using the Symbol() method. 545 func (interp *Interpreter) EvalTest(path string) error { 546 _, err := interp.importSrc(mainID, path, Test) 547 return err 548 } 549 550 func isFile(filesystem fs.FS, path string) bool { 551 fi, err := fs.Stat(filesystem, path) 552 return err == nil && fi.Mode().IsRegular() 553 } 554 555 func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, err error) { 556 prog, err := interp.compileSrc(src, name, inc) 557 if err != nil { 558 return res, err 559 } 560 561 if interp.noRun { 562 return res, err 563 } 564 565 return interp.Execute(prog) 566 } 567 568 // EvalWithContext evaluates Go code represented as a string. It returns 569 // a map on current interpreted package exported symbols. 570 func (interp *Interpreter) EvalWithContext(ctx context.Context, src string) (reflect.Value, error) { 571 var v reflect.Value 572 var err error 573 574 interp.mutex.Lock() 575 interp.done = make(chan struct{}) 576 interp.cancelChan = !interp.opt.fastChan 577 interp.mutex.Unlock() 578 579 done := make(chan struct{}) 580 go func() { 581 defer func() { 582 if r := recover(); r != nil { 583 var pc [64]uintptr 584 n := runtime.Callers(1, pc[:]) 585 err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()} 586 } 587 close(done) 588 }() 589 v, err = interp.Eval(src) 590 }() 591 592 select { 593 case <-ctx.Done(): 594 interp.stop() 595 return reflect.Value{}, ctx.Err() 596 case <-done: 597 } 598 return v, err 599 } 600 601 // stop sends a semaphore to all running frames and closes the chan 602 // operation short circuit channel. stop may only be called once per 603 // invocation of EvalWithContext. 604 func (interp *Interpreter) stop() { 605 atomic.AddUint64(&interp.id, 1) 606 close(interp.done) 607 } 608 609 func (interp *Interpreter) runid() uint64 { return atomic.LoadUint64(&interp.id) } 610 611 // ignoreScannerError returns true if the error from Go scanner can be safely ignored 612 // to let the caller grab one more line before retrying to parse its input. 613 func ignoreScannerError(e *scanner.Error, s string) bool { 614 msg := e.Msg 615 if strings.HasSuffix(msg, "found 'EOF'") { 616 return true 617 } 618 if msg == "raw string literal not terminated" { 619 return true 620 } 621 if strings.HasPrefix(msg, "expected operand, found '}'") && !strings.HasSuffix(s, "}") { 622 return true 623 } 624 return false 625 } 626 627 // ImportUsed automatically imports pre-compiled packages included by Use(). 628 // This is mainly useful for REPLs, or single command lines. In case of an ambiguous default 629 // package name, for example "rand" for crypto/rand and math/rand, the package name is 630 // constructed by replacing the last "/" by a "_", producing crypto_rand and math_rand. 631 // ImportUsed should not be called more than once, and not after a first Eval, as it may 632 // rename packages. 633 func (interp *Interpreter) ImportUsed() { 634 sc := interp.universe 635 for k := range interp.binPkg { 636 // By construction, the package name is the last path element of the key. 637 name := path.Base(k) 638 if sym, ok := sc.sym[name]; ok { 639 // Handle collision by renaming old and new entries. 640 name2 := key2name(fixKey(sym.typ.path)) 641 sc.sym[name2] = sym 642 if name2 != name { 643 delete(sc.sym, name) 644 } 645 name = key2name(fixKey(k)) 646 } 647 sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: k, scope: sc}} 648 } 649 } 650 651 func key2name(name string) string { 652 return filepath.Join(name, DefaultSourceName) 653 } 654 655 func fixKey(k string) string { 656 i := strings.LastIndex(k, "/") 657 if i >= 0 { 658 k = k[:i] + "_" + k[i+1:] 659 } 660 return k 661 } 662 663 // REPL performs a Read-Eval-Print-Loop on input reader. 664 // Results are printed to the output writer of the Interpreter, provided as option 665 // at creation time. Errors are printed to the similarly defined errors writer. 666 // The last interpreter result value and error are returned. 667 func (interp *Interpreter) REPL() (reflect.Value, error) { 668 in, out, errs := interp.stdin, interp.stdout, interp.stderr 669 ctx, cancel := context.WithCancel(context.Background()) 670 end := make(chan struct{}) // channel to terminate the REPL 671 sig := make(chan os.Signal, 1) // channel to trap interrupt signal (Ctrl-C) 672 lines := make(chan string) // channel to read REPL input lines 673 prompt := getPrompt(in, out) // prompt activated on tty like IO stream 674 s := bufio.NewScanner(in) // read input stream line by line 675 var v reflect.Value // result value from eval 676 var err error // error from eval 677 src := "" // source string to evaluate 678 679 signal.Notify(sig, os.Interrupt) 680 defer signal.Stop(sig) 681 prompt(v) 682 683 go func() { 684 defer close(end) 685 for s.Scan() { 686 lines <- s.Text() 687 } 688 if e := s.Err(); e != nil { 689 fmt.Fprintln(errs, e) 690 } 691 }() 692 693 go func() { 694 for { 695 select { 696 case <-sig: 697 cancel() 698 lines <- "" 699 case <-end: 700 return 701 } 702 } 703 }() 704 705 for { 706 var line string 707 708 select { 709 case <-end: 710 cancel() 711 return v, err 712 case line = <-lines: 713 src += line + "\n" 714 } 715 716 v, err = interp.EvalWithContext(ctx, src) 717 if err != nil { 718 switch e := err.(type) { 719 case scanner.ErrorList: 720 if len(e) > 0 && ignoreScannerError(e[0], line) { 721 continue 722 } 723 fmt.Fprintln(errs, strings.TrimPrefix(e[0].Error(), DefaultSourceName+":")) 724 case Panic: 725 fmt.Fprintln(errs, e.Value) 726 fmt.Fprintln(errs, string(e.Stack)) 727 default: 728 fmt.Fprintln(errs, err) 729 } 730 } 731 if errors.Is(err, context.Canceled) { 732 ctx, cancel = context.WithCancel(context.Background()) 733 } 734 src = "" 735 prompt(v) 736 } 737 } 738 739 func doPrompt(out io.Writer) func(v reflect.Value) { 740 return func(v reflect.Value) { 741 if v.IsValid() { 742 fmt.Fprintln(out, ":", v) 743 } 744 fmt.Fprint(out, "> ") 745 } 746 } 747 748 // getPrompt returns a function which prints a prompt only if input is a terminal. 749 func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) { 750 forcePrompt, _ := strconv.ParseBool(os.Getenv("YAEGI_PROMPT")) 751 if forcePrompt { 752 return doPrompt(out) 753 } 754 s, ok := in.(interface{ Stat() (os.FileInfo, error) }) 755 if !ok { 756 return func(reflect.Value) {} 757 } 758 stat, err := s.Stat() 759 if err == nil && stat.Mode()&os.ModeCharDevice != 0 { 760 return doPrompt(out) 761 } 762 return func(reflect.Value) {} 763 }