github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/cmd/lgo-internal/kernel.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"math/rand"
    11  	"os"
    12  	"runtime/debug"
    13  	"strings"
    14  	"time"
    15  	"unicode/utf8"
    16  
    17  	"github.com/golang/glog"
    18  	"github.com/yunabe/lgo/cmd/lgo-internal/liner"
    19  	"github.com/yunabe/lgo/cmd/runner"
    20  	"github.com/yunabe/lgo/converter"
    21  	"github.com/yunabe/lgo/core"
    22  	scaffold "github.com/yunabe/lgo/jupyter/gojupyterscaffold"
    23  )
    24  
    25  type handlers struct {
    26  	runner    *runner.LgoRunner
    27  	execCount int
    28  }
    29  
    30  func (*handlers) HandleKernelInfo() scaffold.KernelInfo {
    31  	return scaffold.KernelInfo{
    32  		ProtocolVersion:       "5.2",
    33  		Implementation:        "lgo",
    34  		ImplementationVersion: "0.0.1",
    35  		LanguageInfo: scaffold.KernelLanguageInfo{
    36  			Name: "go",
    37  		},
    38  		Banner: "lgo",
    39  	}
    40  }
    41  
    42  func pipeOutput(send func(string), file **os.File, done chan<- struct{}) (close func() error, err error) {
    43  	r, w, err := os.Pipe()
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	orig := *file
    48  	*file = w
    49  	go func() {
    50  		utf8R := newUTF8AwareReader(r)
    51  		var buf [4096]byte
    52  		for {
    53  			n, err := utf8R.Read(buf[:])
    54  			if n > 0 {
    55  				send(string(buf[:n]))
    56  			}
    57  			if err != nil {
    58  				if err != io.EOF {
    59  					glog.Error(err)
    60  				}
    61  				break
    62  			}
    63  		}
    64  		if err := r.Close(); err != nil {
    65  			glog.Errorf("Failed to close a reader pipe: %v", err)
    66  		}
    67  		done <- struct{}{}
    68  	}()
    69  	close = func() error {
    70  		*file = orig
    71  		return w.Close()
    72  	}
    73  	return close, nil
    74  }
    75  
    76  type jupyterDisplayer func(data *scaffold.DisplayData, update bool)
    77  
    78  func init() {
    79  	// Initialize the seed to use it from display.
    80  	rand.Seed(time.Now().UnixNano())
    81  }
    82  
    83  func (d jupyterDisplayer) display(data *scaffold.DisplayData, id *string) {
    84  	update := false
    85  	if id != nil {
    86  		if *id == "" {
    87  			var buf [16]byte
    88  			rand.Read(buf[:])
    89  			var enc [32]byte
    90  			hex.Encode(enc[:], buf[:])
    91  			*id = "displayid_" + string(enc[:])
    92  		} else {
    93  			update = true
    94  		}
    95  		if data.Transient == nil {
    96  			data.Transient = make(map[string]interface{})
    97  		}
    98  		data.Transient["display_id"] = *id
    99  	}
   100  	d(data, update)
   101  }
   102  
   103  func (d jupyterDisplayer) Raw(contentType string, v interface{}, id *string) error {
   104  	_, err := json.Marshal(v)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	d.display(&scaffold.DisplayData{
   109  		Data: map[string]interface{}{
   110  			contentType: v,
   111  		},
   112  	}, id)
   113  	return nil
   114  }
   115  
   116  func (d jupyterDisplayer) displayString(contentType, content string, id *string) {
   117  	d.display(&scaffold.DisplayData{
   118  		Data: map[string]interface{}{
   119  			contentType: content,
   120  		},
   121  	}, id)
   122  }
   123  
   124  func (d jupyterDisplayer) displayBytes(contentType string, content []byte, id *string) {
   125  	d.display(&scaffold.DisplayData{
   126  		Data: map[string]interface{}{
   127  			contentType: content,
   128  		},
   129  	}, id)
   130  }
   131  
   132  func (d jupyterDisplayer) JavaScript(s string, id *string) {
   133  	d.displayString("application/javascript", s, id)
   134  }
   135  func (d jupyterDisplayer) HTML(s string, id *string)     { d.displayString("text/html", s, id) }
   136  func (d jupyterDisplayer) Markdown(s string, id *string) { d.displayString("text/markdown", s, id) }
   137  func (d jupyterDisplayer) Latex(s string, id *string)    { d.displayString("text/latex", s, id) }
   138  func (d jupyterDisplayer) SVG(s string, id *string)      { d.displayString("image/svg+xml", s, id) }
   139  func (d jupyterDisplayer) PNG(b []byte, id *string)      { d.displayBytes("image/png", b, id) }
   140  func (d jupyterDisplayer) JPEG(b []byte, id *string)     { d.displayBytes("image/jpeg", b, id) }
   141  func (d jupyterDisplayer) GIF(b []byte, id *string)      { d.displayBytes("image/gif", b, id) }
   142  func (d jupyterDisplayer) PDF(b []byte, id *string)      { d.displayBytes("application/pdf", b, id) }
   143  func (d jupyterDisplayer) Text(s string, id *string)     { d.displayString("text/plain", s, id) }
   144  
   145  func (h *handlers) HandleExecuteRequest(ctx context.Context, r *scaffold.ExecuteRequest, stream func(string, string), displayData func(data *scaffold.DisplayData, update bool)) *scaffold.ExecuteResult {
   146  	h.execCount++
   147  	rDone := make(chan struct{})
   148  	soClose, err := pipeOutput(func(msg string) {
   149  		stream("stdout", msg)
   150  	}, &os.Stdout, rDone)
   151  	if err != nil {
   152  		glog.Errorf("Failed to open stdout pipe: %v", err)
   153  		return &scaffold.ExecuteResult{
   154  			Status:         "error",
   155  			ExecutionCount: h.execCount,
   156  		}
   157  	}
   158  	seClose, err := pipeOutput(func(msg string) {
   159  		stream("stderr", msg)
   160  	}, &os.Stderr, rDone)
   161  	if err != nil {
   162  		glog.Errorf("Failed to open stderr pipe: %v", err)
   163  		return &scaffold.ExecuteResult{
   164  			Status:         "error",
   165  			ExecutionCount: h.execCount,
   166  		}
   167  	}
   168  	lgoCtx := core.LgoContext{
   169  		Context: ctx, Display: jupyterDisplayer(displayData),
   170  	}
   171  	func() {
   172  		defer func() {
   173  			p := recover()
   174  			if p != nil {
   175  				// The return value of debug.Stack() ends with \n.
   176  				fmt.Fprintf(os.Stderr, "panic: %v\n\n%s", p, debug.Stack())
   177  			}
   178  		}()
   179  		// Print the err in the notebook
   180  		if err = h.runner.Run(lgoCtx, r.Code); err != nil {
   181  			runner.PrintError(os.Stderr, err)
   182  		}
   183  	}()
   184  	soClose()
   185  	seClose()
   186  	<-rDone
   187  	<-rDone
   188  	if err != nil {
   189  		return &scaffold.ExecuteResult{
   190  			Status:         "error",
   191  			ExecutionCount: h.execCount,
   192  		}
   193  	}
   194  	return &scaffold.ExecuteResult{
   195  		Status:         "ok",
   196  		ExecutionCount: h.execCount,
   197  	}
   198  }
   199  
   200  func runeOffsetToByteOffset(s string, roff int) int {
   201  	var runes int
   202  	for boff := range s {
   203  		runes++
   204  		if runes > roff {
   205  			return boff
   206  		}
   207  	}
   208  	return len(s)
   209  }
   210  
   211  func (h *handlers) HandleComplete(req *scaffold.CompleteRequest) *scaffold.CompleteReply {
   212  	// Not implemented
   213  	offset := runeOffsetToByteOffset(req.Code, req.CursorPos)
   214  	matches, start, end := h.runner.Complete(context.Background(), req.Code, offset)
   215  	if len(matches) == 0 {
   216  		return nil
   217  	}
   218  	runeStart := utf8.RuneCountInString(req.Code[:start])
   219  	runeEnd := runeStart + utf8.RuneCountInString(req.Code[start:end])
   220  	return &scaffold.CompleteReply{
   221  		Matches:     matches,
   222  		Status:      "ok",
   223  		CursorStart: runeStart,
   224  		CursorEnd:   runeEnd,
   225  	}
   226  }
   227  
   228  func (h *handlers) HandleInspect(r *scaffold.InspectRequest) *scaffold.InspectReply {
   229  	doc, err := h.runner.Inspect(context.Background(), r.Code, runeOffsetToByteOffset(r.Code, r.CursorPos))
   230  	if err != nil {
   231  		glog.Errorf("Failed to inspect: %v", err)
   232  		return nil
   233  	}
   234  	if doc == "" {
   235  		return nil
   236  	}
   237  	return &scaffold.InspectReply{
   238  		Status: "ok",
   239  		Found:  true,
   240  		Data: map[string]interface{}{
   241  			"text/plain": doc,
   242  		},
   243  	}
   244  }
   245  
   246  func (*handlers) HandleIsComplete(req *scaffold.IsCompleteRequest) *scaffold.IsCompleteReply {
   247  	cont, indent := liner.ContinueLineString(req.Code)
   248  	if cont {
   249  		return &scaffold.IsCompleteReply{
   250  			Status: "incomplete",
   251  			// Use 4-spaces instead of "\t" because jupyter console prints "^I" for "\t".
   252  			Indent: strings.Repeat("    ", indent),
   253  		}
   254  	}
   255  	return &scaffold.IsCompleteReply{
   256  		Status: "complete",
   257  	}
   258  }
   259  
   260  func (*handlers) HandleGoFmt(req *scaffold.GoFmtRequest) (*scaffold.GoFmtReply, error) {
   261  	formatted, err := converter.Format(req.Code)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	return &scaffold.GoFmtReply{
   266  		Status: "ok",
   267  		Code:   formatted,
   268  	}, nil
   269  }
   270  
   271  // kernelLogWriter forwards messages to the current os.Stderr, which is change on every execution.
   272  type kernelLogWriter struct{}
   273  
   274  func (kernelLogWriter) Write(p []byte) (n int, err error) {
   275  	return os.Stderr.Write(p)
   276  }
   277  
   278  type glogLogger struct{}
   279  
   280  func (*glogLogger) Info(msg string) {
   281  	// 0: here, 1: loggerWrapper methods, 2: caller
   282  	glog.InfoDepth(2, msg)
   283  }
   284  func (*glogLogger) Warning(msg string) {
   285  	glog.WarningDepth(2, msg)
   286  }
   287  func (*glogLogger) Error(msg string) {
   288  	glog.ErrorDepth(2, msg)
   289  }
   290  func (*glogLogger) Fatal(msg string) {
   291  	glog.FatalDepth(2, msg)
   292  }
   293  
   294  func kernelMain(lgopath string, sessID *runner.SessionID) {
   295  	log.SetOutput(kernelLogWriter{})
   296  	scaffold.SetLogger(&glogLogger{})
   297  	server, err := scaffold.NewServer(context.Background(), *connectionFile, &handlers{
   298  		runner: runner.NewLgoRunner(lgopath, sessID),
   299  	})
   300  	if err != nil {
   301  		glog.Fatalf("Failed to create a server: %v", err)
   302  	}
   303  
   304  	// Start the server loop
   305  	server.Loop()
   306  	// clean-up
   307  	glog.Infof("Clean the session: %s", sessID.Marshal())
   308  	runner.CleanSession(lgopath, sessID)
   309  }