diff --git a/_test/const17.go b/_test/const17.go new file mode 100644 index 00000000..45d741a7 --- /dev/null +++ b/_test/const17.go @@ -0,0 +1,14 @@ +package main + +import "fmt" + +var t [7/3]int + +func main() { + t[0] = 3/2 + t[1] = 5/2 + fmt.Println(t) +} + +// Output: +// [1 2] diff --git a/cmd/yaegi/yaegi.go b/cmd/yaegi/yaegi.go index 7377e78f..01c1384d 100644 --- a/cmd/yaegi/yaegi.go +++ b/cmd/yaegi/yaegi.go @@ -72,6 +72,9 @@ Options: include unsafe symbols. Debugging support (may be removed at any time): + YAEGI_PROMPT=1 + Force enable the printing of the REPL prompt and the result of last instruction, + even if stdin is not a terminal. YAEGI_AST_DOT=1 Generate and display graphviz dot of AST with dotty(1) YAEGI_CFG_DOT=1 diff --git a/internal/cmd/genop/genop.go b/internal/cmd/genop/genop.go index 5d71dc30..8452def8 100644 --- a/internal/cmd/genop/genop.go +++ b/internal/cmd/genop/genop.go @@ -196,6 +196,18 @@ func {{$name}}Const(n *node) { {{- if $op.Shift}} v := constant.Shift(vConstantValue(v0), token.{{tokenFromName $name}}, uint(vUint(v1))) n.rval.Set(reflect.ValueOf(v)) + {{- else if (eq $op.Name "/")}} + var operator token.Token + // When the result of the operation is expected to be an int (because both + // operands are ints), we want to force the type of the whole expression to be an + // int (and not a float), which is achieved by using the QUO_ASSIGN operator. + if n.typ.untyped && isInt(n.typ.rtype) { + operator = token.QUO_ASSIGN + } else { + operator = token.QUO + } + v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1)) + n.rval.Set(reflect.ValueOf(v)) {{- else}} v := constant.BinaryOp(vConstantValue(v0), token.{{tokenFromName $name}}, vConstantValue(v1)) n.rval.Set(reflect.ValueOf(v)) diff --git a/interp/interp.go b/interp/interp.go index 5ab4b74c..92761afa 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -785,20 +785,28 @@ func (interp *Interpreter) REPL() (reflect.Value, error) { } } +func doPrompt(out io.Writer) func(v reflect.Value) { + return func(v reflect.Value) { + if v.IsValid() { + fmt.Fprintln(out, ":", v) + } + fmt.Fprint(out, "> ") + } +} + // getPrompt returns a function which prints a prompt only if input is a terminal. func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) { + forcePrompt, _ := strconv.ParseBool(os.Getenv("YAEGI_PROMPT")) + if forcePrompt { + return doPrompt(out) + } s, ok := in.(interface{ Stat() (os.FileInfo, error) }) if !ok { return func(reflect.Value) {} } stat, err := s.Stat() if err == nil && stat.Mode()&os.ModeCharDevice != 0 { - return func(v reflect.Value) { - if v.IsValid() { - fmt.Fprintln(out, ":", v) - } - fmt.Fprint(out, "> ") - } + return doPrompt(out) } return func(reflect.Value) {} } diff --git a/interp/interp_eval_test.go b/interp/interp_eval_test.go index 1b1e0da2..cda87e8f 100644 --- a/interp/interp_eval_test.go +++ b/interp/interp_eval_test.go @@ -1331,3 +1331,91 @@ func applyCIMultiplier(timeout time.Duration) time.Duration { } return time.Duration(float64(timeout) * CITimeoutMultiplier) } + +func TestREPLDivision(t *testing.T) { + _ = os.Setenv("YAEGI_PROMPT", "1") + defer func() { + _ = os.Setenv("YAEGI_PROMPT", "0") + }() + allDone := make(chan bool) + runREPL := func() { + done := make(chan error) + pinin, poutin := io.Pipe() + pinout, poutout := io.Pipe() + i := interp.New(interp.Options{Stdin: pinin, Stdout: poutout}) + i.Use(stdlib.Symbols) + + go func() { + _, _ = i.REPL() + }() + + defer func() { + _ = pinin.Close() + _ = poutin.Close() + _ = pinout.Close() + _ = poutout.Close() + allDone <- true + }() + + input := []string{ + `1/1`, + `7/3`, + `16/5`, + `3./2`, // float + } + output := []string{ + `1`, + `2`, + `3`, + `1.5`, + } + + go func() { + sc := bufio.NewScanner(pinout) + k := 0 + for sc.Scan() { + l := sc.Text() + if l != "> : "+output[k] { + done <- fmt.Errorf("unexpected output, want %q, got %q", output[k], l) + return + } + k++ + if k > 3 { + break + } + } + done <- nil + }() + + for _, v := range input { + in := strings.NewReader(v + "\n") + if _, err := io.Copy(poutin, in); err != nil { + t.Fatal(err) + } + select { + case err := <-done: + if err != nil { + t.Fatal(err) + } + return + default: + time.Sleep(time.Second) + } + } + + if err := <-done; err != nil { + t.Fatal(err) + } + } + + go func() { + runREPL() + }() + + timeout := time.NewTimer(10 * time.Second) + select { + case <-allDone: + case <-timeout.C: + t.Fatal("timeout") + } +} diff --git a/interp/op.go b/interp/op.go index 68ab74c7..7e1719bb 100644 --- a/interp/op.go +++ b/interp/op.go @@ -701,7 +701,16 @@ func quoConst(n *node) { n.rval = reflect.New(t).Elem() switch { case isConst: - v := constant.BinaryOp(vConstantValue(v0), token.QUO, vConstantValue(v1)) + var operator token.Token + // When the result of the operation is expected to be an int (because both + // operands are ints), we want to force the type of the whole expression to be an + // int (and not a float), which is achieved by using the QUO_ASSIGN operator. + if n.typ.untyped && isInt(n.typ.rtype) { + operator = token.QUO_ASSIGN + } else { + operator = token.QUO + } + v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1)) n.rval.Set(reflect.ValueOf(v)) case isComplex(t): n.rval.SetComplex(vComplex(v0) / vComplex(v1)) diff --git a/interp/typecheck.go b/interp/typecheck.go index 1eda4508..055193b7 100644 --- a/interp/typecheck.go +++ b/interp/typecheck.go @@ -1040,24 +1040,10 @@ func (check typecheck) convertUntyped(n *node, typ *itype) error { return convErr } - isFloatToIntDivision := false if err := check.representable(n, rtyp); err != nil { - if !isInt(rtyp) || n.action != aQuo { - return err - } - // retry in the case of a division, and pretend we want a float. Because if we - // can represent a float, then it follows that we can represent the integer - // part of that float as an int. - if err := check.representable(n, reflect.TypeOf(1.0)); err != nil { - return err - } - isFloatToIntDivision = true - } - if isFloatToIntDivision { - n.rval, err = check.convertConstFloatToInt(n.rval) - } else { - n.rval, err = check.convertConst(n.rval, rtyp) + return err } + n.rval, err = check.convertConst(n.rval, rtyp) if err != nil { if errors.Is(err, errCantConvert) { return convErr @@ -1099,22 +1085,6 @@ func (check typecheck) representable(n *node, t reflect.Type) error { return nil } -func (check typecheck) convertConstFloatToInt(v reflect.Value) (reflect.Value, error) { - if !v.IsValid() { - return v, errors.New("invalid float reflect value") - } - c, ok := v.Interface().(constant.Value) - if !ok { - return v, errors.New("unexpected non-constant value") - } - - if constant.ToFloat(c).Kind() != constant.Float { - return v, errors.New("const value cannot be converted to float") - } - fl, _ := constant.Float64Val(c) - return reflect.ValueOf(int(fl)).Convert(reflect.TypeOf(1.0)), nil -} - func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) { if !v.IsValid() { // TODO(nick): This should be an error as the const is in the frame which is undesirable.