[llgo] llgoi: separate evaluation from printing

Summary:
Separate the evaluation of expressions from printing
of results. This is in preparation for splitting the
core of the interpreter out for use in alternative
interpreter frontends.

At the same time, the output is made less noisy in
response to comments on the golang-nuts announcement.
We would ideally print out values using Go syntax,
but this is impractical until we have libgo based on
Go 1.5. When that happens, fmt's %#v will handle
reflect.Value better, and so we can fix/filter type
names to remove automatically generated package names.

Reviewers: pcc

Subscribers: llvm-commits, axw

Differential Revision: http://reviews.llvm.org/D13761

llvm-svn: 267374
GitOrigin-RevId: bfb167960380a7a05063ae8ecc09dd9619719d5f
diff --git a/cmd/llgoi/llgoi.go b/cmd/llgoi/llgoi.go
index 401e8f3..4aa774d 100644
--- a/cmd/llgoi/llgoi.go
+++ b/cmd/llgoi/llgoi.go
@@ -83,8 +83,9 @@
 	imports []*types.Package
 	scope   map[string]types.Object
 
-	pkgmap map[string]*types.Package
-	pkgnum int
+	modules map[string]llvm.Module
+	pkgmap  map[string]*types.Package
+	pkgnum  int
 }
 
 func (in *interp) makeCompilerOptions() error {
@@ -119,6 +120,7 @@
 	in.liner = liner.NewLiner()
 	in.scope = make(map[string]types.Object)
 	in.pkgmap = make(map[string]*types.Package)
+	in.modules = make(map[string]llvm.Module)
 
 	err := in.makeCompilerOptions()
 	if err != nil {
@@ -139,23 +141,21 @@
 	if err != nil {
 		return nil, err
 	}
-
 	files := []*ast.File{file}
-
 	return in.loadSourcePackage(fset, files, pkgpath, copts)
 }
 
-func (in *interp) loadSourcePackage(fset *token.FileSet, files []*ast.File, pkgpath string, copts irgen.CompilerOptions) (pkg *types.Package, err error) {
+func (in *interp) loadSourcePackage(fset *token.FileSet, files []*ast.File, pkgpath string, copts irgen.CompilerOptions) (_ *types.Package, resultErr error) {
 	compiler, err := irgen.NewCompiler(copts)
 	if err != nil {
-		return
+		return nil, err
 	}
 
 	module, err := compiler.Compile(fset, files, pkgpath)
 	if err != nil {
-		return
+		return nil, err
 	}
-	pkg = module.Package
+	in.modules[pkgpath] = module.Module
 
 	if in.engine.C != nil {
 		in.engine.AddModule(module.Module)
@@ -163,25 +163,33 @@
 		options := llvm.NewMCJITCompilerOptions()
 		in.engine, err = llvm.NewMCJITCompiler(module.Module, options)
 		if err != nil {
-			return
+			return nil, err
 		}
 	}
 
-	importname := irgen.ManglePackagePath(pkgpath) + "..import$descriptor"
-	importglobal := module.Module.NamedGlobal(importname)
-
-	var importfunc func()
-	*(*unsafe.Pointer)(unsafe.Pointer(&importfunc)) = in.engine.PointerToGlobal(importglobal)
+	var importFunc func()
+	importAddress := in.getPackageSymbol(pkgpath, ".import$descriptor")
+	*(*unsafe.Pointer)(unsafe.Pointer(&importFunc)) = importAddress
 
 	defer func() {
 		p := recover()
 		if p != nil {
-			err = fmt.Errorf("panic: %v\n%v", p, string(debug.Stack()))
+			resultErr = fmt.Errorf("panic: %v\n%v", p, string(debug.Stack()))
 		}
 	}()
-	importfunc()
-	in.pkgmap[pkgpath] = pkg
-	return
+	importFunc()
+	in.pkgmap[pkgpath] = module.Package
+
+	return module.Package, nil
+}
+
+func (in *interp) getPackageSymbol(pkgpath, name string) unsafe.Pointer {
+	symbolName := irgen.ManglePackagePath(pkgpath) + "." + name
+	global := in.modules[pkgpath].NamedGlobal(symbolName)
+	if global.IsNil() {
+		return nil
+	}
+	return in.engine.PointerToGlobal(global)
 }
 
 func (in *interp) augmentPackageScope(pkg *types.Package) {
@@ -246,19 +254,17 @@
 	return l.parens <= 0 && l.bracks <= 0 && l.braces <= 0
 }
 
-func (in *interp) readExprLine(str string, assigns []string) error {
+func (in *interp) readExprLine(str string, assigns []string) ([]interface{}, error) {
 	in.pendingLine.append(str, assigns)
-
-	if in.pendingLine.ready() {
-		err := in.interpretLine(in.pendingLine)
-		in.pendingLine = line{}
-		return err
-	} else {
-		return nil
+	if !in.pendingLine.ready() {
+		return nil, nil
 	}
+	results, err := in.interpretLine(in.pendingLine)
+	in.pendingLine = line{}
+	return results, err
 }
 
-func (in *interp) interpretLine(l line) error {
+func (in *interp) interpretLine(l line) ([]interface{}, error) {
 	pkgname := fmt.Sprintf("input%05d", in.pkgnum)
 	in.pkgnum++
 
@@ -277,14 +283,12 @@
 		var err error
 		tv, err = types.Eval(l.line, pkg, scope)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
 	var code bytes.Buffer
-	fmt.Fprintf(&code, "package %s", pkgname)
-	code.WriteString("\n\nimport __fmt__ \"fmt\"\n")
-	code.WriteString("import __os__ \"os\"\n")
+	fmt.Fprintf(&code, "package %s\n", pkgname)
 
 	for _, pkg := range in.imports {
 		fmt.Fprintf(&code, "import %q\n", pkg.Path())
@@ -306,7 +310,7 @@
 			typs = append(typs, types.Typ[types.Bool])
 		}
 		if len(l.assigns) != 0 && len(l.assigns) != len(typs) {
-			return errors.New("return value mismatch")
+			return nil, errors.New("return value mismatch")
 		}
 
 		code.WriteString("var ")
@@ -324,31 +328,34 @@
 				fmt.Fprintf(&code, "__llgoiV%d", i)
 			}
 		}
-		fmt.Fprintf(&code, " = %s\n\n", l.line)
+		fmt.Fprintf(&code, " = %s\n", l.line)
 
-		code.WriteString("func init() {\n\t")
-		for i, t := range typs {
-			var varname, prefix string
+		code.WriteString("func init() {\n")
+		varnames := make([]string, len(typs))
+		for i := range typs {
+			var varname string
 			if len(l.assigns) != 0 && l.assigns[i] != "" {
 				if _, ok := in.scope[l.assigns[i]]; ok {
 					fmt.Fprintf(&code, "\t%s = __llgoiV%d\n", l.assigns[i], i)
 				}
 				varname = l.assigns[i]
-				prefix = l.assigns[i]
 			} else {
 				varname = fmt.Sprintf("__llgoiV%d", i)
-				prefix = fmt.Sprintf("#%d", i)
 			}
-			if _, ok := t.Underlying().(*types.Interface); ok {
-				fmt.Fprintf(&code, "\t__fmt__.Printf(\"%s %s (%%T) = %%+v\\n\", %s, %s)\n", prefix, t.String(), varname, varname)
-			} else {
-				fmt.Fprintf(&code, "\t__fmt__.Printf(\"%s %s = %%+v\\n\", %s)\n", prefix, t.String(), varname)
-			}
+			varnames[i] = varname
 		}
-		code.WriteString("}")
+		code.WriteString("}\n\n")
+
+		code.WriteString("func __llgoiResults() []interface{} {\n")
+		code.WriteString("\treturn []interface{}{\n")
+		for _, varname := range varnames {
+			fmt.Fprintf(&code, "\t\t%s,\n", varname)
+		}
+		code.WriteString("\t}\n")
+		code.WriteString("}\n")
 	} else {
 		if len(l.assigns) != 0 {
-			return errors.New("return value mismatch")
+			return nil, errors.New("return value mismatch")
 		}
 
 		fmt.Fprintf(&code, "func init() {\n\t%s}", l.line)
@@ -359,11 +366,18 @@
 	copts.DisableUnusedImportCheck = true
 	pkg, err := in.loadSourcePackageFromCode(code.String(), pkgname, copts)
 	if err != nil {
-		return err
+		return nil, err
 	}
-
 	in.imports = append(in.imports, pkg)
 
+	var results []interface{}
+	llgoiResultsAddress := in.getPackageSymbol(pkgname, "__llgoiResults$descriptor")
+	if llgoiResultsAddress != nil {
+		var resultsFunc func() []interface{}
+		*(*unsafe.Pointer)(unsafe.Pointer(&resultsFunc)) = llgoiResultsAddress
+		results = resultsFunc()
+	}
+
 	for _, assign := range l.assigns {
 		if assign != "" {
 			if _, ok := in.scope[assign]; !ok {
@@ -376,7 +390,7 @@
 		in.scope[l.declName] = pkg.Scope().Lookup(l.declName)
 	}
 
-	return nil
+	return results, nil
 }
 
 func (in *interp) maybeReadAssignment(line string, s *scanner.Scanner, initial string, base int) (bool, error) {
@@ -404,7 +418,9 @@
 		return false, nil
 	}
 
-	return true, in.readExprLine(line[int(pos)-base+2:], assigns)
+	// It's an assignment statement, there are no results.
+	_, err := in.readExprLine(line[int(pos)-base+2:], assigns)
+	return true, err
 }
 
 func (in *interp) loadPackage(pkgpath string) (*types.Package, error) {
@@ -428,8 +444,6 @@
 		}
 	}
 
-	fmt.Printf("# %s\n", pkgpath)
-
 	inputs := make([]string, len(buildpkg.GoFiles))
 	for i, file := range buildpkg.GoFiles {
 		inputs[i] = filepath.Join(buildpkg.Dir, file)
@@ -446,7 +460,7 @@
 
 // readLine accumulates lines of input, including trailing newlines,
 // executing statements as they are completed.
-func (in *interp) readLine(line string) error {
+func (in *interp) readLine(line string) ([]interface{}, error) {
 	if !in.pendingLine.ready() {
 		return in.readExprLine(line, nil)
 	}
@@ -459,33 +473,32 @@
 	_, tok, lit := s.Scan()
 	switch tok {
 	case token.EOF:
-		return nil
+		return nil, nil
 
 	case token.IMPORT:
 		_, tok, lit = s.Scan()
 		if tok != token.STRING {
-			return errors.New("expected string literal")
+			return nil, errors.New("expected string literal")
 		}
 		pkgpath, err := strconv.Unquote(lit)
 		if err != nil {
-			return err
+			return nil, err
 		}
 		pkg, err := in.loadPackage(pkgpath)
 		if err != nil {
-			return err
+			return nil, err
 		}
 		in.imports = append(in.imports, pkg)
-		return nil
+		return nil, nil
 
 	case token.IDENT:
 		ok, err := in.maybeReadAssignment(line, &s, lit, file.Base())
 		if err != nil {
-			return err
+			return nil, err
 		}
 		if ok {
-			return nil
+			return nil, nil
 		}
-
 		fallthrough
 
 	default:
@@ -493,6 +506,14 @@
 	}
 }
 
+// printResult prints a value that was the result of an expression evaluated
+// by the interpreter.
+func printResult(w io.Writer, v interface{}) {
+	// TODO the result should be formatted in Go syntax, without
+	// package qualifiers for types defined within the interpreter.
+	fmt.Fprintf(w, "%+v", v)
+}
+
 // formatHistory reformats the provided Go source by collapsing all lines
 // and adding semicolons where required, suitable for adding to line history.
 func formatHistory(input []byte) string {
@@ -560,10 +581,14 @@
 			continue
 		}
 		buf.WriteString(line + "\n")
-		err = in.readLine(line + "\n")
+		results, err := in.readLine(line + "\n")
 		if err != nil {
 			fmt.Println(err)
 		}
+		for _, result := range results {
+			printResult(os.Stdout, result)
+			fmt.Println()
+		}
 	}
 
 	if liner.TerminalSupported() {
diff --git a/test/llgoi/arith.test b/test/llgoi/arith.test
index 83b30d9..b7869a9 100644
--- a/test/llgoi/arith.test
+++ b/test/llgoi/arith.test
@@ -1,4 +1,4 @@
 // RUN: llgoi < %s | FileCheck %s
 
 1 + 1
-// CHECK: #0 untyped int = 2
+// CHECK: 2
diff --git a/test/llgoi/import-source.test b/test/llgoi/import-source.test
index b545b52..3619fb4 100644
--- a/test/llgoi/import-source.test
+++ b/test/llgoi/import-source.test
@@ -4,17 +4,15 @@
 Answer := 1
 
 import "foo"
-// CHECK: # bar
-// CHECK: # foo
 
 // Test that importing binary after source works.
 import "strconv"
 
 foo.Answer()
-// CHECK: #0 int = 42
+// CHECK: 42
 
 strconv.FormatBool(true)
-// CHECK: #0 string = true
+// CHECK: true
 
 var v1 strconv.NumError
 var v2 strconv.NumError
diff --git a/test/llgoi/import-source2.test b/test/llgoi/import-source2.test
index fcf3b47..0be45ee 100644
--- a/test/llgoi/import-source2.test
+++ b/test/llgoi/import-source2.test
@@ -4,11 +4,9 @@
 import "strconv"
 
 import "foo"
-// CHECK: # bar
-// CHECK: # foo
 
 foo.Answer()
-// CHECK: #0 int = 42
+// CHECK: 42
 
 strconv.FormatBool(true)
-// CHECK: #0 string = true
+// CHECK: true
diff --git a/test/llgoi/interfaces.test b/test/llgoi/interfaces.test
index bc12b09..125e015 100644
--- a/test/llgoi/interfaces.test
+++ b/test/llgoi/interfaces.test
@@ -2,18 +2,21 @@
 
 import "errors"
 err := errors.New("foo")
-// CHECK: err error {{.*}} = foo
+err
+// CHECK: foo
 
 err.(interface{Foo()})
 // CHECK: panic: interface conversion
 
-_, _ := err.(interface{Foo()})
-// CHECK: #0 interface{Foo()} (<nil>) = <nil>
-// CHECK: #1 bool = false
+_, ok := err.(interface{Foo()})
+ok
+// CHECK: false
 
 err.(interface{Error() string})
-// CHECK: #0 interface{Error() string} {{.*}} = foo
+// CHECK: foo
 
-_, _ := err.(interface{Error() string})
-// CHECK: #0 interface{Error() string} {{.*}} = foo
-// CHECK: #1 bool = true
+iface, ok := err.(interface{Error() string})
+iface
+// CHECK: foo
+ok
+// CHECK: true
diff --git a/test/llgoi/maps.test b/test/llgoi/maps.test
index 8678104..ab1ba7e 100644
--- a/test/llgoi/maps.test
+++ b/test/llgoi/maps.test
@@ -1,22 +1,27 @@
 // RUN: llgoi < %s | FileCheck %s
 
 m := make(map[int]int)
-// CHECK: m map[int]int = map[]
+m
+// CHECK: map[]
 
 m[0]
-// CHECK: #0 int = 0
+// CHECK: 0
 
-_, _ := m[0]
-// CHECK: #0 int = 0
-// CHECK: #1 bool = false
+m0, ok := m[0]
+m0
+// CHECK: 0
+ok
+// CHECK: false
 
 func() {
 	m[0] = 1
 }()
 
 m[0]
-// CHECK: #0 int = 1
+// CHECK: 1
 
-_, _ := m[0]
-// CHECK: #0 int = 1
-// CHECK: #1 bool = true
+m0, ok = m[0]
+m0
+// CHECK: 1
+ok
+// CHECK: true
diff --git a/test/llgoi/vars.test b/test/llgoi/vars.test
index 1e1f063..c28198c 100644
--- a/test/llgoi/vars.test
+++ b/test/llgoi/vars.test
@@ -1,17 +1,18 @@
 // RUN: llgoi < %s 2>&1 | FileCheck %s
 
 x := 3
-// CHECK: x untyped int = 3
+x
+// CHECK: 3
 
 x + x
-// CHECK: #0 int = 6
+// CHECK: 6
 
 x * x
-// CHECK: #0 int = 9
+// CHECK: 9
 
 x = 4
 x + x
-// CHECK: #0 int = 8
+// CHECK: 8
 
 x := true
 // CHECK: cannot assign {{.*}} to x (variable of type int)
@@ -19,11 +20,11 @@
 x, y := func() (int, int) {
 	return 1, 2
 }()
-// CHECK: x int = 1
-// CHECK: y int = 2
+// CHECK: 1
+// CHECK: 2
 
 x, _ = func() (int, int) {
 	return 3, 4
 }()
 x
-// CHECK: #0 int = 3
+// CHECK: 3