github.com/google/skylark@v0.0.0-20181101142754-a5f7082aabed/repl/repl.go (about) 1 // The repl package provides a read/eval/print loop for Skylark. 2 // 3 // It supports readline-style command editing, 4 // and interrupts through Control-C. 5 // 6 // If an input line can be parsed as an expression, 7 // the REPL parses and evaluates it and prints its result. 8 // Otherwise the REPL reads lines until a blank line, 9 // then tries again to parse the multi-line input as an 10 // expression. If the input still cannot be parsed as an expression, 11 // the REPL parses and executes it as a file (a list of statements), 12 // for side effects. 13 package repl 14 15 // TODO(adonovan): 16 // 17 // - Unparenthesized tuples are not parsed as a single expression: 18 // >>> (1, 2) 19 // (1, 2) 20 // >>> 1, 2 21 // ... 22 // >>> 23 // This is not necessarily a bug. 24 25 import ( 26 "bytes" 27 "context" 28 "fmt" 29 "os" 30 "os/signal" 31 "strings" 32 33 "github.com/chzyer/readline" 34 "github.com/google/skylark" 35 "github.com/google/skylark/syntax" 36 ) 37 38 var interrupted = make(chan os.Signal, 1) 39 40 // REPL executes a read, eval, print loop. 41 // 42 // Before evaluating each expression, it sets the Skylark thread local 43 // variable named "context" to a context.Context that is cancelled by a 44 // SIGINT (Control-C). Client-supplied global functions may use this 45 // context to make long-running operations interruptable. 46 // 47 func REPL(thread *skylark.Thread, globals skylark.StringDict) { 48 signal.Notify(interrupted, os.Interrupt) 49 defer signal.Stop(interrupted) 50 51 rl, err := readline.New(">>> ") 52 if err != nil { 53 PrintError(err) 54 return 55 } 56 defer rl.Close() 57 for { 58 if err := rep(rl, thread, globals); err != nil { 59 if err == readline.ErrInterrupt { 60 fmt.Println(err) 61 continue 62 } 63 break 64 } 65 } 66 fmt.Println() 67 } 68 69 // rep reads, evaluates, and prints one item. 70 // 71 // It returns an error (possibly readline.ErrInterrupt) 72 // only if readline failed. Skylark errors are printed. 73 func rep(rl *readline.Instance, thread *skylark.Thread, globals skylark.StringDict) error { 74 // Each item gets its own context, 75 // which is cancelled by a SIGINT. 76 // 77 // Note: during Readline calls, Control-C causes Readline to return 78 // ErrInterrupt but does not generate a SIGINT. 79 ctx, cancel := context.WithCancel(context.Background()) 80 defer cancel() 81 go func() { 82 select { 83 case <-interrupted: 84 cancel() 85 case <-ctx.Done(): 86 } 87 }() 88 89 thread.SetLocal("context", ctx) 90 91 rl.SetPrompt(">>> ") 92 line, err := rl.Readline() 93 if err != nil { 94 return err // may be ErrInterrupt 95 } 96 97 if l := strings.TrimSpace(line); l == "" || l[0] == '#' { 98 return nil // blank or comment 99 } 100 101 // If the line contains a well-formed expression, evaluate it. 102 if _, err := syntax.ParseExpr("<stdin>", line, 0); err == nil { 103 if v, err := skylark.Eval(thread, "<stdin>", line, globals); err != nil { 104 PrintError(err) 105 } else if v != skylark.None { 106 fmt.Println(v) 107 } 108 return nil 109 } 110 111 // If the input so far is a single load or assignment statement, 112 // execute it without waiting for a blank line. 113 if f, err := syntax.Parse("<stdin>", line, 0); err == nil && len(f.Stmts) == 1 { 114 switch f.Stmts[0].(type) { 115 case *syntax.AssignStmt, *syntax.LoadStmt: 116 // Execute it as a file. 117 if err := execFileNoFreeze(thread, line, globals); err != nil { 118 PrintError(err) 119 } 120 return nil 121 } 122 } 123 124 // Otherwise assume it is the first of several 125 // comprising a file, followed by a blank line. 126 var buf bytes.Buffer 127 fmt.Fprintln(&buf, line) 128 for { 129 rl.SetPrompt("... ") 130 line, err := rl.Readline() 131 if err != nil { 132 return err // may be ErrInterrupt 133 } 134 if l := strings.TrimSpace(line); l == "" { 135 break // blank 136 } 137 fmt.Fprintln(&buf, line) 138 } 139 text := buf.Bytes() 140 141 // Try parsing it once more as an expression, 142 // such as a call spread over several lines: 143 // f( 144 // 1, 145 // 2 146 // ) 147 if _, err := syntax.ParseExpr("<stdin>", text, 0); err == nil { 148 if v, err := skylark.Eval(thread, "<stdin>", text, globals); err != nil { 149 PrintError(err) 150 } else if v != skylark.None { 151 fmt.Println(v) 152 } 153 return nil 154 } 155 156 // Execute it as a file. 157 if err := execFileNoFreeze(thread, text, globals); err != nil { 158 PrintError(err) 159 } 160 161 return nil 162 } 163 164 // execFileNoFreeze is skylark.ExecFile without globals.Freeze(). 165 func execFileNoFreeze(thread *skylark.Thread, src interface{}, globals skylark.StringDict) error { 166 _, prog, err := skylark.SourceProgram("<stdin>", src, globals.Has) 167 if err != nil { 168 return err 169 } 170 171 res, err := prog.Init(thread, globals) 172 173 // The global names from the previous call become 174 // the predeclared names of this call. 175 176 // Copy globals back to the caller's map. 177 // If execution failed, some globals may be undefined. 178 for k, v := range res { 179 globals[k] = v 180 } 181 182 return err 183 } 184 185 // PrintError prints the error to stderr, 186 // or its backtrace if it is a Skylark evaluation error. 187 func PrintError(err error) { 188 if evalErr, ok := err.(*skylark.EvalError); ok { 189 fmt.Fprintln(os.Stderr, evalErr.Backtrace()) 190 } else { 191 fmt.Fprintln(os.Stderr, err) 192 } 193 } 194 195 // MakeLoad returns a simple sequential implementation of module loading 196 // suitable for use in the REPL. 197 // Each function returned by MakeLoad accesses a distinct private cache. 198 func MakeLoad() func(thread *skylark.Thread, module string) (skylark.StringDict, error) { 199 type entry struct { 200 globals skylark.StringDict 201 err error 202 } 203 204 var cache = make(map[string]*entry) 205 206 return func(thread *skylark.Thread, module string) (skylark.StringDict, error) { 207 e, ok := cache[module] 208 if e == nil { 209 if ok { 210 // request for package whose loading is in progress 211 return nil, fmt.Errorf("cycle in load graph") 212 } 213 214 // Add a placeholder to indicate "load in progress". 215 cache[module] = nil 216 217 // Load it. 218 thread := &skylark.Thread{Load: thread.Load} 219 globals, err := skylark.ExecFile(thread, module, nil, nil) 220 e = &entry{globals, err} 221 222 // Update the cache. 223 cache[module] = e 224 } 225 return e.globals, e.err 226 } 227 }