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  }