Skip to content

Commit

Permalink
Merge pull request #268 from visualfc/repl
Browse files Browse the repository at this point in the history
repl: support literal eval
  • Loading branch information
visualfc authored Mar 11, 2024
2 parents c643b36 + 1f93b07 commit 05e3440
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 28 deletions.
72 changes: 48 additions & 24 deletions repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"go/scanner"
"go/token"
"go/types"
"reflect"
"runtime"
"strings"

Expand Down Expand Up @@ -53,19 +54,37 @@ type Repl struct {
imports []string // import lines
globals []string // global var/func/type
infuncs []string // in main func
lastDump []string // last __igop_repl_dump__
lastEval []*Eval // last __igop_repl_eval__
}

func toDump(i []interface{}) (dump []string) {
type Eval struct {
Value interface{} // constant.Value or interface{}
Type reflect.Type
}

func (e *Eval) String() string {
if c, ok := e.Value.(constant.Value); ok {
s, _ := xconst.ExactConstantEx(c, true)
if c.Kind() == constant.Float {
es := c.ExactString()
return fmt.Sprintf("%v untyped %v\n(%v)", s, e.Type, es)
} else {
return fmt.Sprintf("%v untyped %v", s, e.Type)
}
}
return fmt.Sprintf("%v %v", e.Value, e.Type)
}

func toEval(i []interface{}) (eval []*Eval) {
for _, v := range i {
dump = append(dump, fmt.Sprintf("%v %T", v, v))
eval = append(eval, &Eval{v, reflect.TypeOf(v)})
}
return
}

func toResultDump(interp *Interp, vs []interface{}, rs *types.Tuple) (dump []string) {
func toResultEval(interp *Interp, vs []interface{}, rs *types.Tuple) (eval []*Eval) {
for i, v := range vs {
dump = append(dump, fmt.Sprintf("%v %v", v, interp.toType(rs.At(i).Type())))
eval = append(eval, &Eval{v, interp.toType(rs.At(i).Type())})
}
return
}
Expand All @@ -77,8 +96,8 @@ func NewRepl(ctx *Context) *Repl {
}
ctx.SetEvalMode(true)
RegisterCustomBuiltin("__igop_repl_used__", func(v interface{}) {})
RegisterCustomBuiltin("__igop_repl_dump__", func(v ...interface{}) {
r.lastDump = toDump(v)
RegisterCustomBuiltin("__igop_repl_eval__", func(v ...interface{}) {
r.lastEval = toEval(v)
})
// reset runtime.GC to default
ctx.RegisterExternal("runtime.GC", runtime.GC)
Expand All @@ -93,7 +112,7 @@ func NewRepl(ctx *Context) *Repl {
if strings.Contains(v.Name(), "__igop_repl_") {
return
}
r.lastDump = toResultDump(interp, res, call.Call.Signature().Results())
r.lastEval = toResultEval(interp, res, call.Call.Signature().Results())
}
if f, err := ParseBuiltin(r.ctx.FileSet, "main"); err == nil {
r.builtin = f
Expand All @@ -105,11 +124,11 @@ func (r *Repl) SetFileName(filename string) {
r.fileName = filename
}

func (r *Repl) Eval(expr string) (tok token.Token, dump []string, err error) {
r.lastDump = nil
func (r *Repl) Eval(expr string) (tok token.Token, dump []*Eval, err error) {
r.lastEval = nil
tok = r.firstToken(expr)
err = r.eval(tok, expr)
return tok, r.lastDump, err
return tok, r.lastEval, err
}

func (r *Repl) Interp() *Interp {
Expand Down Expand Up @@ -188,9 +207,14 @@ func (r *Repl) eval(tok token.Token, expr string) (err error) {
return errors[0]
}
default:
orgExpr := expr
switch tok {
case token.FOR, token.IF, token.SWITCH, token.SELECT:
expr = "func(){\n" + expr + "\n}()"
case token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING,
token.ADD, token.SUB, token.NOT, token.XOR,
token.LPAREN, token.LBRACK, token.LBRACE:
expr = "__igop_repl_eval__(" + expr + ")"
}
inMain = true
src = r.buildSource(expr, tok)
Expand All @@ -207,16 +231,22 @@ func (r *Repl) eval(tok token.Token, expr string) (err error) {
if strings.HasSuffix(e.Msg, errDeclaredNotUsed) {
v := e.Msg[0 : len(e.Msg)-len(errDeclaredNotUsed)-1]
fixed = append(fixed, "__igop_repl_used__(&"+v+")")
// fixed = append(fixed, "__igop_repl_dump__("+v+")")
// fixed = append(fixed, "__igop_repl_eval__("+v+")")
} else if strings.HasSuffix(e.Msg, errIsNotUsed) {
if _, ok := extractConstant([]byte(e.Msg[:len(e.Msg)-len(errIsNotUsed)])); ok {
// r.lastDump = []string{c.Lit}
// return nil
expr = "const __igop_repl_const__ = " + expr
expr = "const __igop_repl_const__ = " + orgExpr
tok = token.CONST
evalConst = true
} else {
expr = "__igop_repl_dump__(" + expr + ")"
expr = "__igop_repl_eval__(" + orgExpr + ")"
}
} else if strings.HasSuffix(e.Msg, errDumpOverflows) {
if _, ok := extractConstant([]byte(e.Msg[:len(e.Msg)-len(errDumpOverflows)])); ok {
expr = "const __igop_repl_const__ = " + orgExpr
tok = token.CONST
evalConst = true
} else {
return e
}
} else {
return e
Expand Down Expand Up @@ -246,14 +276,7 @@ func (r *Repl) eval(tok token.Token, expr string) (err error) {
if evalConst {
m := i.mainpkg.Members["__igop_repl_const__"]
c, _ := m.(*ssa.NamedConst)
s, _ := xconst.ExactConstantEx(c.Value.Value, true)
kind := c.Value.Value.Kind()
if kind == constant.Float {
es := c.Value.Value.ExactString()
r.lastDump = []string{fmt.Sprintf("%v %v\n(%v)", s, c.Type(), es)}
} else {
r.lastDump = []string{fmt.Sprintf("%v %v", s, c.Type())}
}
r.lastEval = []*Eval{&Eval{c.Value.Value, i.toType(c.Type())}}
return nil
}
r.interp = i
Expand All @@ -273,6 +296,7 @@ func (r *Repl) eval(tok token.Token, expr string) (err error) {

const (
errIsNotUsed = "is not used"
errDumpOverflows = "to __igop_repl_eval__ (overflows)"
errMaybeGoFunLit = `expected 'IDENT', found '{'`
errMaybeGopFunLit = `expected '(',`
)
Expand Down
12 changes: 8 additions & 4 deletions repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (r *REPL) Run(line string) error {
}
expr = line
}
tok, dump, err := r.Eval(expr)
tok, eval, err := r.Eval(expr)
if err != nil {
if checkMore(tok, err) {
r.more += "\n" + line
Expand All @@ -217,12 +217,16 @@ func (r *REPL) Run(line string) error {
}
return err
}
switch len(dump) {
switch len(eval) {
case 0:
case 1:
r.Printf("%v\n", dump[0])
r.Printf("%v\n", eval[0])
default:
r.Printf("(%v)\n", strings.Join(dump, ", "))
var info []string
for _, v := range eval {
info = append(info, v.String())
}
r.Printf("(%v)\n", strings.Join(info, ", "))
}
r.SetNormal()
return nil
Expand Down
36 changes: 36 additions & 0 deletions repl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,39 @@ func TestReplSelect(t *testing.T) {
}
}
}

func TestReplLiteral(t *testing.T) {
ctx := igop.NewContext(0)
repl := igop.NewRepl(ctx)
list := []string{
`100`,
`100.0*200`,
`(100*200)`,
`map[int]string{1:"100"}`,
`1e100<<2`,
`+100*2`,
`-100/2`,
`^100`,
`"hello"+"world"`,
}
result := []string{
`[100 int]`,
`[20000 float64]`,
`[20000 int]`,
`[map[1:100] map[int]string]`,
`[40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 untyped int]`,
`[200 int]`,
`[-50 int]`,
`[-101 int]`,
`[helloworld string]`,
}
for i, expr := range list {
_, v, err := repl.Eval(expr)
if err != nil {
t.Fatal(err)
}
if fmt.Sprint(v) != result[i] {
t.Fatalf("expr:%v dump:%v src:%v", expr, v, repl.Source())
}
}
}

0 comments on commit 05e3440

Please sign in to comment.