github.com/goplus/igop@v0.25.0/repl/repl.go (about)

     1  /*
     2   * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package repl
    18  
    19  import (
    20  	"fmt"
    21  	"go/token"
    22  	"os/exec"
    23  	"reflect"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/goplus/igop"
    28  	"github.com/goplus/igop/constant"
    29  	"golang.org/x/tools/go/ssa"
    30  )
    31  
    32  const (
    33  	// ContinuePrompt - the current code statement is not completed.
    34  	ContinuePrompt string = "... "
    35  	// NormalPrompt - start of a code statement.
    36  	NormalPrompt string = ">>> "
    37  )
    38  
    39  type UI interface {
    40  	SetPrompt(prompt string)
    41  	Printf(format string, a ...interface{})
    42  }
    43  
    44  type REPL struct {
    45  	*igop.Repl
    46  	term UI
    47  	more string
    48  }
    49  
    50  func NewREPL(mode igop.Mode) *REPL {
    51  	r := &REPL{}
    52  	igop.RegisterCustomBuiltin("__igop_repl_info__", func(v interface{}) {
    53  		r.Printf("%v %T\n", v, v)
    54  	})
    55  	ctx := igop.NewContext(mode)
    56  	r.Repl = igop.NewRepl(ctx)
    57  	return r
    58  }
    59  
    60  func (r *REPL) SetUI(term UI) {
    61  	r.term = term
    62  	term.SetPrompt(NormalPrompt)
    63  }
    64  
    65  func (r *REPL) SetNormal() {
    66  	r.more = ""
    67  	r.SetPrompt(NormalPrompt)
    68  }
    69  
    70  func (r *REPL) SetPrompt(prompt string) {
    71  	if r.term != nil {
    72  		r.term.SetPrompt(prompt)
    73  	}
    74  }
    75  
    76  func (r *REPL) IsNormal() bool {
    77  	return r.more == ""
    78  }
    79  
    80  func (r *REPL) TryDump(expr string) bool {
    81  	i := r.Interp()
    82  	if i != nil {
    83  		if m, v, ok := i.GetSymbol(expr); ok {
    84  			switch p := m.(type) {
    85  			case *ssa.NamedConst:
    86  				r.Printf("const %v %v\n", constant.ExactConstant(p.Value.Value), p.Type())
    87  			case *ssa.Global:
    88  				e := reflect.ValueOf(v).Elem().Interface()
    89  				r.Printf("%v %T (global var)\n", e, e)
    90  			case *ssa.Type:
    91  				if m.Package().Pkg.Name() == "main" {
    92  					return false
    93  				}
    94  				if r.tryDumpByPkg(expr) != nil {
    95  					r.Printf("%v %v\n", p.Type().Underlying(), v)
    96  				}
    97  			case *ssa.Function:
    98  				n := p.Signature.Params().Len()
    99  				if n == 0 || (n == 1 && p.Signature.Variadic()) {
   100  					return false
   101  				}
   102  				r.Printf("%v %v\n", v, p.Type())
   103  			}
   104  			return true
   105  		}
   106  	}
   107  	return false
   108  }
   109  
   110  func (r *REPL) Dump(expr string) {
   111  	i := r.Interp()
   112  	if i != nil {
   113  		if m, v, ok := i.GetSymbol(expr); ok {
   114  			switch p := m.(type) {
   115  			case *ssa.NamedConst:
   116  				r.Printf("const %v %v\n", constant.ExactConstant(p.Value.Value), p.Type())
   117  			case *ssa.Global:
   118  				e := reflect.ValueOf(v).Elem().Interface()
   119  				r.Printf("%v %T (global var)\n", e, e)
   120  			case *ssa.Type:
   121  				if r.tryDumpByPkg(expr) != nil {
   122  					r.Printf("%v %v\n", p.Type().Underlying(), v)
   123  				}
   124  			case *ssa.Function:
   125  				r.Printf("%v %v\n", v, p.Type())
   126  			}
   127  			return
   128  		}
   129  	}
   130  	_, _, err := r.Eval(fmt.Sprintf("__igop_repl_info__(%v)", expr))
   131  	if err == nil {
   132  		return
   133  	}
   134  	if err = r.godoc(expr); err == nil {
   135  		return
   136  	}
   137  	if err = r.tryDumpByPkg(expr); err == nil {
   138  		return
   139  	}
   140  	r.Printf("not found %v\n", expr)
   141  }
   142  
   143  func (r *REPL) tryDumpByPkg(expr string) error {
   144  	pkg, sym, _, err := parseSymbol(expr)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	if pkgPath, found := findPkg(pkg); found {
   149  		if p, found := igop.LookupPackage(pkgPath); found {
   150  			if sym == "" {
   151  				r.Printf("%v\n", dumpPkg(p))
   152  				return nil
   153  			}
   154  			if info, ok := lookupSymbol(p, sym); ok {
   155  				r.Printf("%v\n", info)
   156  				return nil
   157  			}
   158  			return fmt.Errorf("not found symbol %v.%v", pkg, sym)
   159  		}
   160  	}
   161  	return fmt.Errorf("not found pkg %v", pkg)
   162  }
   163  
   164  func (r *REPL) godoc(expr string) error {
   165  	gobin, err := exec.LookPath("go")
   166  	if err != nil {
   167  		return err
   168  	}
   169  	cmd := exec.Command(gobin, "doc", expr)
   170  	data, err := cmd.CombinedOutput()
   171  	if err != nil {
   172  		return err
   173  	}
   174  	r.Printf("%v\n", string(data))
   175  	return nil
   176  }
   177  
   178  func (r *REPL) Printf(_fmt string, a ...interface{}) {
   179  	if r.term != nil {
   180  		r.term.Printf(_fmt, a...)
   181  	} else {
   182  		fmt.Printf(_fmt, a...)
   183  	}
   184  }
   185  
   186  var (
   187  	regWord = regexp.MustCompile("\\w+")
   188  )
   189  
   190  func (r *REPL) Run(line string) error {
   191  	var expr string
   192  	if r.more != "" {
   193  		if line == "" {
   194  			r.SetNormal()
   195  			return nil
   196  		}
   197  		expr = r.more + "\n" + line
   198  	} else {
   199  		if strings.HasPrefix(line, "?") {
   200  			r.Dump(strings.TrimSpace(line[1:]))
   201  			return nil
   202  		} else if regWord.MatchString(line) {
   203  			if r.TryDump(line) {
   204  				return nil
   205  			}
   206  		}
   207  		expr = line
   208  	}
   209  	tok, eval, err := r.Eval(expr)
   210  	if err != nil {
   211  		if checkMore(tok, err) {
   212  			r.more += "\n" + line
   213  			r.SetPrompt(ContinuePrompt)
   214  			return nil
   215  		} else {
   216  			r.SetNormal()
   217  		}
   218  		return err
   219  	}
   220  	switch len(eval) {
   221  	case 0:
   222  	case 1:
   223  		r.Printf("%v\n", eval[0])
   224  	default:
   225  		var info []string
   226  		for _, v := range eval {
   227  			info = append(info, v.String())
   228  		}
   229  		r.Printf("(%v)\n", strings.Join(info, ", "))
   230  	}
   231  	r.SetNormal()
   232  	return nil
   233  }
   234  
   235  func checkMore(tok token.Token, err error) bool {
   236  	s := err.Error()
   237  	if strings.Contains(s, `expected `) {
   238  		return true
   239  	}
   240  	return false
   241  }