github.com/prattmic/llgo-embedded@v0.0.0-20150820070356-41cfecea0e1e/cmd/llgoi/llgoi.go (about)

     1  //===- llgoi.go - llgo-based Go REPL --------------------------------------===//
     2  //
     3  //                     The LLVM Compiler Infrastructure
     4  //
     5  // This file is distributed under the University of Illinois Open Source
     6  // License. See LICENSE.TXT for details.
     7  //
     8  //===----------------------------------------------------------------------===//
     9  //
    10  // This is llgoi, a Go REPL based on llgo and the LLVM JIT.
    11  //
    12  //===----------------------------------------------------------------------===//
    13  
    14  package main
    15  
    16  import (
    17  	"bytes"
    18  	"errors"
    19  	"fmt"
    20  	"go/ast"
    21  	"go/build"
    22  	"go/parser"
    23  	"go/scanner"
    24  	"go/token"
    25  	"io"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"runtime/debug"
    30  	"strconv"
    31  	"strings"
    32  	"unsafe"
    33  
    34  	"llvm.org/llgo/driver"
    35  	"llvm.org/llgo/irgen"
    36  	"llvm.org/llgo/third_party/gotools/go/types"
    37  	"llvm.org/llgo/third_party/liner"
    38  	"llvm.org/llvm/bindings/go/llvm"
    39  )
    40  
    41  func getInstPrefix() (string, error) {
    42  	path, err := exec.LookPath(os.Args[0])
    43  	if err != nil {
    44  		return "", err
    45  	}
    46  
    47  	path, err = filepath.EvalSymlinks(path)
    48  	if err != nil {
    49  		return "", err
    50  	}
    51  
    52  	prefix := filepath.Join(path, "..", "..")
    53  	return prefix, nil
    54  }
    55  
    56  func llvmVersion() string {
    57  	return strings.Replace(llvm.Version, "svn", "", 1)
    58  }
    59  
    60  type line struct {
    61  	line     string
    62  	isStmt   bool
    63  	declName string
    64  	assigns  []string
    65  
    66  	parens, bracks, braces int
    67  }
    68  
    69  type interp struct {
    70  	engine llvm.ExecutionEngine
    71  
    72  	liner       *liner.State
    73  	pendingLine line
    74  
    75  	copts irgen.CompilerOptions
    76  
    77  	imports []*types.Package
    78  	scope   map[string]types.Object
    79  
    80  	pkgmap map[string]*types.Package
    81  	pkgnum int
    82  }
    83  
    84  func (in *interp) makeCompilerOptions() error {
    85  	prefix, err := getInstPrefix()
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	importPaths := []string{filepath.Join(prefix, "lib", "go", "llgo-"+llvmVersion())}
    91  	in.copts = irgen.CompilerOptions{
    92  		TargetTriple:  llvm.DefaultTargetTriple(),
    93  		ImportPaths:   importPaths,
    94  		GenerateDebug: true,
    95  		Packages:      in.pkgmap,
    96  	}
    97  	err = in.copts.MakeImporter()
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	origImporter := in.copts.Importer
   103  	in.copts.Importer = func(pkgmap map[string]*types.Package, pkgpath string) (*types.Package, error) {
   104  		if pkg, ok := pkgmap[pkgpath]; ok && pkg.Complete() {
   105  			return pkg, nil
   106  		}
   107  		return origImporter(pkgmap, pkgpath)
   108  	}
   109  	return nil
   110  }
   111  
   112  func (in *interp) init() error {
   113  	in.liner = liner.NewLiner()
   114  	in.scope = make(map[string]types.Object)
   115  	in.pkgmap = make(map[string]*types.Package)
   116  
   117  	err := in.makeCompilerOptions()
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  func (in *interp) dispose() {
   126  	in.liner.Close()
   127  	in.engine.Dispose()
   128  }
   129  
   130  func (in *interp) loadSourcePackageFromCode(pkgcode, pkgpath string, copts irgen.CompilerOptions) (*types.Package, error) {
   131  	fset := token.NewFileSet()
   132  	file, err := parser.ParseFile(fset, "<input>", pkgcode, parser.DeclarationErrors|parser.ParseComments)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	files := []*ast.File{file}
   138  
   139  	return in.loadSourcePackage(fset, files, pkgpath, copts)
   140  }
   141  
   142  func (in *interp) loadSourcePackage(fset *token.FileSet, files []*ast.File, pkgpath string, copts irgen.CompilerOptions) (pkg *types.Package, err error) {
   143  	compiler, err := irgen.NewCompiler(copts)
   144  	if err != nil {
   145  		return
   146  	}
   147  
   148  	module, err := compiler.Compile(fset, files, pkgpath)
   149  	if err != nil {
   150  		return
   151  	}
   152  	pkg = module.Package
   153  
   154  	if in.engine.C != nil {
   155  		in.engine.AddModule(module.Module)
   156  	} else {
   157  		options := llvm.NewMCJITCompilerOptions()
   158  		in.engine, err = llvm.NewMCJITCompiler(module.Module, options)
   159  		if err != nil {
   160  			return
   161  		}
   162  	}
   163  
   164  	importname := irgen.ManglePackagePath(pkgpath) + "..import$descriptor"
   165  	importglobal := module.Module.NamedGlobal(importname)
   166  
   167  	var importfunc func()
   168  	*(*unsafe.Pointer)(unsafe.Pointer(&importfunc)) = in.engine.PointerToGlobal(importglobal)
   169  
   170  	defer func() {
   171  		p := recover()
   172  		if p != nil {
   173  			err = fmt.Errorf("panic: %v\n%v", p, string(debug.Stack()))
   174  		}
   175  	}()
   176  	importfunc()
   177  	in.pkgmap[pkgpath] = pkg
   178  	return
   179  }
   180  
   181  func (in *interp) augmentPackageScope(pkg *types.Package) {
   182  	for _, obj := range in.scope {
   183  		pkg.Scope().Insert(obj)
   184  	}
   185  }
   186  
   187  func (l *line) append(str string, assigns []string) {
   188  	var s scanner.Scanner
   189  	fset := token.NewFileSet()
   190  	file := fset.AddFile("", fset.Base(), len(str))
   191  	s.Init(file, []byte(str), nil, 0)
   192  
   193  	_, tok, _ := s.Scan()
   194  	if l.line == "" {
   195  		switch tok {
   196  		case token.FOR, token.GO, token.IF, token.LBRACE, token.SELECT, token.SWITCH:
   197  			l.isStmt = true
   198  		case token.CONST, token.FUNC, token.TYPE, token.VAR:
   199  			var lit string
   200  			_, tok, lit = s.Scan()
   201  			if tok == token.IDENT {
   202  				l.declName = lit
   203  			}
   204  		}
   205  	}
   206  
   207  	for tok != token.EOF {
   208  		switch tok {
   209  		case token.LPAREN:
   210  			l.parens++
   211  		case token.RPAREN:
   212  			l.parens--
   213  		case token.LBRACE:
   214  			l.braces++
   215  		case token.RBRACE:
   216  			l.braces--
   217  		case token.LBRACK:
   218  			l.bracks++
   219  		case token.RBRACK:
   220  			l.bracks--
   221  		case token.DEC, token.INC,
   222  			token.ASSIGN, token.ADD_ASSIGN, token.SUB_ASSIGN,
   223  			token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN,
   224  			token.AND_ASSIGN, token.OR_ASSIGN, token.XOR_ASSIGN,
   225  			token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN:
   226  			if l.parens == 0 && l.bracks == 0 && l.braces == 0 {
   227  				l.isStmt = true
   228  			}
   229  		}
   230  		_, tok, _ = s.Scan()
   231  	}
   232  
   233  	if l.line == "" {
   234  		l.assigns = assigns
   235  	}
   236  	l.line += str
   237  }
   238  
   239  func (l *line) ready() bool {
   240  	return l.parens <= 0 && l.bracks <= 0 && l.braces <= 0
   241  }
   242  
   243  func (in *interp) readExprLine(str string, assigns []string) error {
   244  	in.pendingLine.append(str, assigns)
   245  
   246  	if in.pendingLine.ready() {
   247  		err := in.interpretLine(in.pendingLine)
   248  		in.pendingLine = line{}
   249  		return err
   250  	} else {
   251  		return nil
   252  	}
   253  }
   254  
   255  func (in *interp) interpretLine(l line) error {
   256  	pkgname := fmt.Sprintf("input%05d", in.pkgnum)
   257  	in.pkgnum++
   258  
   259  	pkg := types.NewPackage(pkgname, pkgname)
   260  	scope := pkg.Scope()
   261  
   262  	for _, imppkg := range in.imports {
   263  		obj := types.NewPkgName(token.NoPos, pkg, imppkg.Name(), imppkg)
   264  		scope.Insert(obj)
   265  	}
   266  
   267  	in.augmentPackageScope(pkg)
   268  
   269  	var tv types.TypeAndValue
   270  	if l.declName == "" && !l.isStmt {
   271  		var err error
   272  		tv, err = types.Eval(l.line, pkg, scope)
   273  		if err != nil {
   274  			return err
   275  		}
   276  	}
   277  
   278  	var code bytes.Buffer
   279  	fmt.Fprintf(&code, "package %s", pkgname)
   280  	code.WriteString("\n\nimport __fmt__ \"fmt\"\n")
   281  	code.WriteString("import __os__ \"os\"\n")
   282  
   283  	for _, pkg := range in.imports {
   284  		fmt.Fprintf(&code, "import %q\n", pkg.Path())
   285  	}
   286  
   287  	if l.declName != "" {
   288  		code.WriteString(l.line)
   289  	} else if !l.isStmt && tv.IsValue() {
   290  		var typs []types.Type
   291  		if tuple, ok := tv.Type.(*types.Tuple); ok {
   292  			typs = make([]types.Type, tuple.Len())
   293  			for i := range typs {
   294  				typs[i] = tuple.At(i).Type()
   295  			}
   296  		} else {
   297  			typs = []types.Type{tv.Type}
   298  		}
   299  		if len(l.assigns) == 2 && tv.HasOk() {
   300  			typs = append(typs, types.Typ[types.Bool])
   301  		}
   302  		if len(l.assigns) != 0 && len(l.assigns) != len(typs) {
   303  			return errors.New("return value mismatch")
   304  		}
   305  
   306  		code.WriteString("var ")
   307  		for i := range typs {
   308  			if i != 0 {
   309  				code.WriteString(", ")
   310  			}
   311  			if len(l.assigns) != 0 && l.assigns[i] != "" {
   312  				if _, ok := in.scope[l.assigns[i]]; ok {
   313  					fmt.Fprintf(&code, "__llgoiV%d", i)
   314  				} else {
   315  					code.WriteString(l.assigns[i])
   316  				}
   317  			} else {
   318  				fmt.Fprintf(&code, "__llgoiV%d", i)
   319  			}
   320  		}
   321  		fmt.Fprintf(&code, " = %s\n\n", l.line)
   322  
   323  		code.WriteString("func init() {\n\t")
   324  		for i, t := range typs {
   325  			var varname, prefix string
   326  			if len(l.assigns) != 0 && l.assigns[i] != "" {
   327  				if _, ok := in.scope[l.assigns[i]]; ok {
   328  					fmt.Fprintf(&code, "\t%s = __llgoiV%d\n", l.assigns[i], i)
   329  				}
   330  				varname = l.assigns[i]
   331  				prefix = l.assigns[i]
   332  			} else {
   333  				varname = fmt.Sprintf("__llgoiV%d", i)
   334  				prefix = fmt.Sprintf("#%d", i)
   335  			}
   336  			if _, ok := t.Underlying().(*types.Interface); ok {
   337  				fmt.Fprintf(&code, "\t__fmt__.Printf(\"%s %s (%%T) = %%+v\\n\", %s, %s)\n", prefix, t.String(), varname, varname)
   338  			} else {
   339  				fmt.Fprintf(&code, "\t__fmt__.Printf(\"%s %s = %%+v\\n\", %s)\n", prefix, t.String(), varname)
   340  			}
   341  		}
   342  		code.WriteString("}")
   343  	} else {
   344  		if len(l.assigns) != 0 {
   345  			return errors.New("return value mismatch")
   346  		}
   347  
   348  		fmt.Fprintf(&code, "func init() {\n\t%s}", l.line)
   349  	}
   350  
   351  	copts := in.copts
   352  	copts.PackageCreated = in.augmentPackageScope
   353  	copts.DisableUnusedImportCheck = true
   354  	pkg, err := in.loadSourcePackageFromCode(code.String(), pkgname, copts)
   355  	if err != nil {
   356  		return err
   357  	}
   358  
   359  	in.imports = append(in.imports, pkg)
   360  
   361  	for _, assign := range l.assigns {
   362  		if assign != "" {
   363  			if _, ok := in.scope[assign]; !ok {
   364  				in.scope[assign] = pkg.Scope().Lookup(assign)
   365  			}
   366  		}
   367  	}
   368  
   369  	if l.declName != "" {
   370  		in.scope[l.declName] = pkg.Scope().Lookup(l.declName)
   371  	}
   372  
   373  	return nil
   374  }
   375  
   376  func (in *interp) maybeReadAssignment(line string, s *scanner.Scanner, initial string, base int) (bool, error) {
   377  	if initial == "_" {
   378  		initial = ""
   379  	}
   380  	assigns := []string{initial}
   381  
   382  	pos, tok, lit := s.Scan()
   383  	for tok == token.COMMA {
   384  		pos, tok, lit = s.Scan()
   385  		if tok != token.IDENT {
   386  			return false, nil
   387  		}
   388  
   389  		if lit == "_" {
   390  			lit = ""
   391  		}
   392  		assigns = append(assigns, lit)
   393  
   394  		pos, tok, lit = s.Scan()
   395  	}
   396  
   397  	if tok != token.DEFINE {
   398  		return false, nil
   399  	}
   400  
   401  	return true, in.readExprLine(line[int(pos)-base+2:], assigns)
   402  }
   403  
   404  func (in *interp) loadPackage(pkgpath string) (*types.Package, error) {
   405  	pkg, err := in.copts.Importer(in.pkgmap, pkgpath)
   406  	if err == nil {
   407  		return pkg, nil
   408  	}
   409  
   410  	buildpkg, err := build.Import(pkgpath, ".", 0)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  	if len(buildpkg.CgoFiles) != 0 {
   415  		return nil, fmt.Errorf("%s: cannot load cgo package", pkgpath)
   416  	}
   417  
   418  	for _, imp := range buildpkg.Imports {
   419  		_, err := in.loadPackage(imp)
   420  		if err != nil {
   421  			return nil, err
   422  		}
   423  	}
   424  
   425  	fmt.Printf("# %s\n", pkgpath)
   426  
   427  	inputs := make([]string, len(buildpkg.GoFiles))
   428  	for i, file := range buildpkg.GoFiles {
   429  		inputs[i] = filepath.Join(buildpkg.Dir, file)
   430  	}
   431  
   432  	fset := token.NewFileSet()
   433  	files, err := driver.ParseFiles(fset, inputs)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  
   438  	return in.loadSourcePackage(fset, files, pkgpath, in.copts)
   439  }
   440  
   441  // readLine accumulates lines of input, including trailing newlines,
   442  // executing statements as they are completed.
   443  func (in *interp) readLine(line string) error {
   444  	if !in.pendingLine.ready() {
   445  		return in.readExprLine(line, nil)
   446  	}
   447  
   448  	var s scanner.Scanner
   449  	fset := token.NewFileSet()
   450  	file := fset.AddFile("", fset.Base(), len(line))
   451  	s.Init(file, []byte(line), nil, 0)
   452  
   453  	_, tok, lit := s.Scan()
   454  	switch tok {
   455  	case token.EOF:
   456  		return nil
   457  
   458  	case token.IMPORT:
   459  		_, tok, lit = s.Scan()
   460  		if tok != token.STRING {
   461  			return errors.New("expected string literal")
   462  		}
   463  		pkgpath, err := strconv.Unquote(lit)
   464  		if err != nil {
   465  			return err
   466  		}
   467  		pkg, err := in.loadPackage(pkgpath)
   468  		if err != nil {
   469  			return err
   470  		}
   471  		in.imports = append(in.imports, pkg)
   472  		return nil
   473  
   474  	case token.IDENT:
   475  		ok, err := in.maybeReadAssignment(line, &s, lit, file.Base())
   476  		if err != nil {
   477  			return err
   478  		}
   479  		if ok {
   480  			return nil
   481  		}
   482  
   483  		fallthrough
   484  
   485  	default:
   486  		return in.readExprLine(line, nil)
   487  	}
   488  }
   489  
   490  // formatHistory reformats the provided Go source by collapsing all lines
   491  // and adding semicolons where required, suitable for adding to line history.
   492  func formatHistory(input []byte) string {
   493  	var buf bytes.Buffer
   494  	var s scanner.Scanner
   495  	fset := token.NewFileSet()
   496  	file := fset.AddFile("", fset.Base(), len(input))
   497  	s.Init(file, input, nil, 0)
   498  	pos, tok, lit := s.Scan()
   499  	for tok != token.EOF {
   500  		if int(pos)-1 > buf.Len() {
   501  			n := int(pos) - 1 - buf.Len()
   502  			buf.WriteString(strings.Repeat(" ", n))
   503  		}
   504  		var semicolon bool
   505  		if tok == token.SEMICOLON {
   506  			semicolon = true
   507  		} else if lit != "" {
   508  			buf.WriteString(lit)
   509  		} else {
   510  			buf.WriteString(tok.String())
   511  		}
   512  		pos, tok, lit = s.Scan()
   513  		if semicolon {
   514  			switch tok {
   515  			case token.RBRACE, token.RPAREN, token.EOF:
   516  			default:
   517  				buf.WriteRune(';')
   518  			}
   519  		}
   520  	}
   521  	return buf.String()
   522  }
   523  
   524  func main() {
   525  	llvm.LinkInMCJIT()
   526  	llvm.InitializeNativeTarget()
   527  	llvm.InitializeNativeAsmPrinter()
   528  
   529  	var in interp
   530  	err := in.init()
   531  	if err != nil {
   532  		panic(err)
   533  	}
   534  	defer in.dispose()
   535  
   536  	var buf bytes.Buffer
   537  	for {
   538  		if in.pendingLine.ready() && buf.Len() > 0 {
   539  			history := formatHistory(buf.Bytes())
   540  			in.liner.AppendHistory(history)
   541  			buf.Reset()
   542  		}
   543  		prompt := "(llgo) "
   544  		if !in.pendingLine.ready() {
   545  			prompt = strings.Repeat(" ", len(prompt))
   546  		}
   547  		line, err := in.liner.Prompt(prompt)
   548  		if err == io.EOF {
   549  			break
   550  		} else if err != nil {
   551  			panic(err)
   552  		}
   553  		if line == "" {
   554  			continue
   555  		}
   556  		buf.WriteString(line + "\n")
   557  		err = in.readLine(line + "\n")
   558  		if err != nil {
   559  			fmt.Println(err)
   560  		}
   561  	}
   562  
   563  	if liner.TerminalSupported() {
   564  		fmt.Println()
   565  	}
   566  }