github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/terminal/starbind/repl.go (about)

     1  package starbind
     2  
     3  // Code in this file is derived from go.starlark.net/repl/repl.go
     4  // Which is licensed under the following copyright:
     5  //
     6  // Copyright (c) 2017 The Bazel Authors.  All rights reserved.
     7  //
     8  // Redistribution and use in source and binary forms, with or without
     9  // modification, are permitted provided that the following conditions are
    10  // met:
    11  //
    12  // 1. Redistributions of source code must retain the above copyright
    13  //    notice, this list of conditions and the following disclaimer.
    14  //
    15  // 2. Redistributions in binary form must reproduce the above copyright
    16  //    notice, this list of conditions and the following disclaimer in the
    17  //    documentation and/or other materials provided with the
    18  //    distribution.
    19  //
    20  // 3. Neither the name of the copyright holder nor the names of its
    21  //    contributors may be used to endorse or promote products derived
    22  //    from this software without specific prior written permission.
    23  //
    24  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    25  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    26  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    27  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    28  // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    29  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    30  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    31  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    32  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    33  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    34  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    35  
    36  import (
    37  	"fmt"
    38  	"io"
    39  	"os"
    40  
    41  	"go.starlark.net/starlark"
    42  	"go.starlark.net/syntax"
    43  
    44  	"github.com/go-delve/liner"
    45  )
    46  
    47  // REPL executes a read, eval, print loop.
    48  func (env *Env) REPL() error {
    49  	thread := env.newThread()
    50  	globals := starlark.StringDict{}
    51  	for k, v := range env.env {
    52  		globals[k] = v
    53  	}
    54  
    55  	rl := liner.NewLiner()
    56  	defer rl.Close()
    57  	for {
    58  		if err := isCancelled(thread); err != nil {
    59  			return err
    60  		}
    61  		if err := rep(rl, thread, globals, env.out); err != nil {
    62  			if err == io.EOF {
    63  				break
    64  			}
    65  			return err
    66  		}
    67  	}
    68  	fmt.Fprintln(env.out)
    69  	return env.exportGlobals(globals)
    70  }
    71  
    72  const (
    73  	normalPrompt = ">>> "
    74  	extraPrompt  = "... "
    75  
    76  	exitCommand = "exit"
    77  )
    78  
    79  // rep reads, evaluates, and prints one item.
    80  //
    81  // It returns an error (possibly readline.ErrInterrupt)
    82  // only if readline failed. Starlark errors are printed.
    83  func rep(rl *liner.State, thread *starlark.Thread, globals starlark.StringDict, out EchoWriter) error {
    84  	defer out.Flush()
    85  	eof := false
    86  
    87  	prompt := normalPrompt
    88  	readline := func() ([]byte, error) {
    89  		line, err := rl.Prompt(prompt)
    90  		out.Echo(prompt + line)
    91  		if line == exitCommand {
    92  			eof = true
    93  			return nil, io.EOF
    94  		}
    95  		rl.AppendHistory(line)
    96  		prompt = extraPrompt
    97  		if err != nil {
    98  			if err == io.EOF {
    99  				eof = true
   100  			}
   101  			return nil, err
   102  		}
   103  		return []byte(line + "\n"), nil
   104  	}
   105  
   106  	// parse
   107  	f, err := syntax.ParseCompoundStmt("<stdin>", readline)
   108  	if err != nil {
   109  		if eof {
   110  			return io.EOF
   111  		}
   112  		printError(err)
   113  		return nil
   114  	}
   115  
   116  	if expr := soleExpr(f); expr != nil {
   117  		//TODO: check for 'exit'
   118  		// eval
   119  		v, err := starlark.EvalExpr(thread, expr, globals)
   120  		if err != nil {
   121  			printError(err)
   122  			return nil
   123  		}
   124  
   125  		// print
   126  		if v != starlark.None {
   127  			fmt.Fprintln(out, v)
   128  		}
   129  	} else {
   130  		// compile
   131  		prog, err := starlark.FileProgram(f, globals.Has)
   132  		if err != nil {
   133  			printError(err)
   134  			return nil
   135  		}
   136  
   137  		// execute (but do not freeze)
   138  		res, err := prog.Init(thread, globals)
   139  		if err != nil {
   140  			printError(err)
   141  		}
   142  
   143  		// The global names from the previous call become
   144  		// the predeclared names of this call.
   145  		// If execution failed, some globals may be undefined.
   146  		for k, v := range res {
   147  			globals[k] = v
   148  		}
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func soleExpr(f *syntax.File) syntax.Expr {
   155  	if len(f.Stmts) == 1 {
   156  		if stmt, ok := f.Stmts[0].(*syntax.ExprStmt); ok {
   157  			return stmt.X
   158  		}
   159  	}
   160  	return nil
   161  }
   162  
   163  // printError prints the error to stderr,
   164  // or its backtrace if it is a Starlark evaluation error.
   165  func printError(err error) {
   166  	if evalErr, ok := err.(*starlark.EvalError); ok {
   167  		fmt.Fprintln(os.Stderr, evalErr.Backtrace())
   168  	} else {
   169  		fmt.Fprintln(os.Stderr, err)
   170  	}
   171  }
   172  
   173  // MakeLoad returns a simple sequential implementation of module loading
   174  // suitable for use in the REPL.
   175  // Each function returned by MakeLoad accesses a distinct private cache.
   176  func MakeLoad() func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
   177  	type entry struct {
   178  		globals starlark.StringDict
   179  		err     error
   180  	}
   181  
   182  	var cache = make(map[string]*entry)
   183  
   184  	return func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
   185  		e, ok := cache[module]
   186  		if e == nil {
   187  			if ok {
   188  				// request for package whose loading is in progress
   189  				return nil, fmt.Errorf("cycle in load graph")
   190  			}
   191  
   192  			// Add a placeholder to indicate "load in progress".
   193  			cache[module] = nil
   194  
   195  			// Load it.
   196  			thread := &starlark.Thread{Name: "exec " + module, Load: thread.Load}
   197  			globals, err := starlark.ExecFile(thread, module, nil, nil)
   198  			e = &entry{globals, err}
   199  
   200  			// Update the cache.
   201  			cache[module] = e
   202  		}
   203  		return e.globals, e.err
   204  	}
   205  }