github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/cmd/golua-repl/luabuffer.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"regexp"
     8  	"strings"
     9  
    10  	"github.com/arnodel/edit"
    11  	"github.com/arnodel/golua/lib"
    12  	"github.com/arnodel/golua/luastrings"
    13  	"github.com/arnodel/golua/runtime"
    14  )
    15  
    16  type luaLineType uint8
    17  
    18  const (
    19  	luaComment luaLineType = iota
    20  	luaInput
    21  	luaStdout
    22  	luaOutput
    23  	luaParseError
    24  	luaRuntimeError
    25  )
    26  
    27  type luaLineData struct {
    28  	index int
    29  	tp    luaLineType
    30  }
    31  
    32  type LuaBuffer struct {
    33  	r            *runtime.Runtime
    34  	stdout       bytes.Buffer
    35  	buf          edit.FileBuffer
    36  	currentIndex int
    37  }
    38  
    39  var _ edit.Buffer = (*LuaBuffer)(nil)
    40  
    41  func NewLuaBuffer() *LuaBuffer {
    42  	b := &LuaBuffer{}
    43  	b.r = runtime.New(&b.stdout)
    44  	lib.LoadAll(b.r)
    45  	b.buf.AppendLine(edit.NewLineFromString("Welcome to Golua REPL! Press [Ctrl-D] to quit.", luaLineData{tp: luaComment}))
    46  	b.buf.AppendLine(edit.NewLineFromString(inputName(0), luaLineData{tp: luaComment}))
    47  	b.buf.AppendLine(edit.NewLineFromString("> ", luaLineData{tp: luaInput}))
    48  	return b
    49  }
    50  
    51  func (b *LuaBuffer) LineCount() int {
    52  	return b.buf.LineCount()
    53  }
    54  
    55  func (b *LuaBuffer) GetLine(l, c int) (edit.Line, error) {
    56  	return b.buf.GetLine(l, c)
    57  }
    58  
    59  func (b *LuaBuffer) InsertRune(r rune, l, c int) error {
    60  	_, err := b.getEditableLine(l, c)
    61  	if err != nil {
    62  		return err
    63  	}
    64  	return b.buf.InsertRune(r, l, c)
    65  }
    66  
    67  // When pasting code, remove "> " at the start of lines.
    68  var promptPtn = regexp.MustCompile(`(?m)^> `)
    69  
    70  func (b *LuaBuffer) InsertString(s string, l, c int) (int, int, error) {
    71  	// s = promptPtn.ReplaceAllLiteralString(s, "")
    72  	return b.buf.InsertString(s, l, c)
    73  }
    74  
    75  func (b *LuaBuffer) InsertLine(l int, line edit.Line) error {
    76  	if l != b.buf.LineCount() {
    77  		_, err := b.getEditableLine(l, -1)
    78  		if err != nil && l != 0 {
    79  			_, err = b.getEditableLine(l-1, -1)
    80  		}
    81  		if err != nil {
    82  			return err
    83  		}
    84  	}
    85  	data := luaLineData{index: b.currentIndex, tp: luaInput}
    86  	line = edit.NewLineFromString("> ", data).MergeWith(line)
    87  
    88  	return b.buf.InsertLine(l, line)
    89  }
    90  
    91  func (b *LuaBuffer) DeleteLine(l int) error {
    92  	if _, err := b.getEditableLine(l, -1); err != nil {
    93  		return err
    94  	}
    95  	return b.buf.DeleteLine(l)
    96  }
    97  
    98  func (b *LuaBuffer) AppendLine(line edit.Line) {
    99  	_ = b.InsertLine(b.LineCount(), line)
   100  }
   101  
   102  func (b *LuaBuffer) SplitLine(l, c int) error {
   103  	line, err := b.getEditableLine(l, c)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	l1, l2 := line.SplitAt(c)
   108  	if err := b.buf.SetLine(l, l1); err != nil {
   109  		return err
   110  	}
   111  
   112  	return b.InsertLine(l+1, l2)
   113  }
   114  
   115  func (b *LuaBuffer) AdvancePos(l, c, dl, dc int) (int, int) {
   116  	l, c = b.buf.AdvancePos(l, c, dl, dc)
   117  	if c < 2 {
   118  		line := b.buf.Line(l)
   119  		data := line.Meta.(luaLineData)
   120  		if data.tp == luaInput {
   121  			if dc >= 0 || l == 0 {
   122  				c = 2
   123  			} else {
   124  				l--
   125  				c = b.buf.Line(l).Len()
   126  			}
   127  		}
   128  	}
   129  	return l, c
   130  }
   131  
   132  func (b *LuaBuffer) DeleteRuneAt(l, c int) error {
   133  	_, err := b.getEditableLine(l, c)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	return b.buf.DeleteRuneAt(l, c)
   138  }
   139  
   140  func (b *LuaBuffer) EndPos() (int, int) {
   141  	return b.buf.EndPos()
   142  }
   143  
   144  func (b *LuaBuffer) MergeLineWithPrevious(l int) error {
   145  	l2, err := b.getEditableLine(l, -1)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	_, err = b.getEditableLine(l-1, -1)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	l2.Runes = l2.Runes[2:]
   154  	b.buf.SetLine(l, l2)
   155  	return b.buf.MergeLineWithPrevious(l)
   156  }
   157  
   158  func (b *LuaBuffer) Save() error {
   159  
   160  	return fmt.Errorf("unimplemented")
   161  }
   162  
   163  func (b *LuaBuffer) WriteTo(w io.Writer) (int64, error) {
   164  	n := 0
   165  	end := b.LineCount()
   166  	start := 0
   167  	if end >= 1000 {
   168  		start = end - 1000
   169  	}
   170  	for i := start; i < end; i++ {
   171  		l := b.buf.Line(i)
   172  		ln, err := fmt.Fprintf(w, "%s\n", l.String())
   173  		n += ln
   174  		if err != nil {
   175  			return int64(n), err
   176  		}
   177  	}
   178  	return int64(n), nil
   179  }
   180  
   181  func (b *LuaBuffer) StyledLineIter(l, c int) edit.StyledLineIter {
   182  	line := b.buf.Line(l)
   183  	meta := line.Meta.(luaLineData)
   184  	style := edit.DefaultStyle
   185  	switch meta.tp {
   186  	case luaInput:
   187  	case luaStdout:
   188  		style = style.Foreground(edit.ColorYellow)
   189  	case luaOutput:
   190  		style = style.Foreground(edit.ColorGreen).Bold(true)
   191  	case luaParseError:
   192  		style = style.Foreground(edit.ColorRed).Bold(true)
   193  	case luaRuntimeError:
   194  		style = style.Foreground(edit.ColorRed).Bold(true)
   195  	case luaComment:
   196  		style = style.Foreground(edit.ColorAqua)
   197  	}
   198  	return edit.NewConstStyleLineIter(line.Iter(c), style)
   199  }
   200  
   201  func (b *LuaBuffer) Kind() string {
   202  	return "luarepl"
   203  }
   204  
   205  func (b *LuaBuffer) StringFromRegion(l0, c0, l1, c1 int) (string, error) {
   206  	return b.buf.StringFromRegion(l0, c0, l1, c1)
   207  }
   208  
   209  func (b *LuaBuffer) getEditableLine(l, c int) (edit.Line, error) {
   210  	line, err := b.buf.GetLine(l, c)
   211  	if err != nil {
   212  		return edit.Line{}, err
   213  	}
   214  	data := line.Meta.(luaLineData)
   215  	if data.index != b.currentIndex || data.tp != luaInput || (c >= 0 && c < 2) {
   216  		return edit.Line{}, fmt.Errorf("read only")
   217  	}
   218  	return line, nil
   219  }
   220  
   221  func (b *LuaBuffer) getChunk(l int) (string, error) {
   222  	line, err := b.getEditableLine(l, -1)
   223  	if err != nil {
   224  		return "", err
   225  	}
   226  	index := line.Meta.(luaLineData).index
   227  	meta := luaLineData{index: index, tp: luaInput}
   228  	for l > 0 {
   229  		if b.buf.Line(l-1).Meta != meta {
   230  			break
   231  		}
   232  		l--
   233  	}
   234  	var builder strings.Builder
   235  	for {
   236  		builder.WriteString(string(b.buf.Line(l).Runes[2:]))
   237  		builder.WriteByte('\n')
   238  		l++
   239  		if l >= b.buf.LineCount() || b.buf.Line(l).Meta != meta {
   240  			break
   241  		}
   242  	}
   243  	return builder.String(), nil
   244  }
   245  
   246  func (b *LuaBuffer) RunCurrent() (bool, error) {
   247  	b.stdout.Reset()
   248  	l := b.buf.LineCount()
   249  	for ; l > 0; l-- {
   250  		if b.buf.Line(l-1).Meta.(luaLineData).tp != luaParseError {
   251  			break
   252  		}
   253  	}
   254  	b.buf.Truncate(l)
   255  	chunk, err := b.getChunk(b.LineCount() - 1)
   256  	if err != nil {
   257  		return false, err
   258  	}
   259  	if strings.TrimSpace(chunk) == "" {
   260  		return false, nil
   261  	}
   262  	clos, err := b.r.CompileAndLoadLuaChunkOrExp(inputName(b.currentIndex), []byte(chunk), runtime.TableValue(b.r.GlobalEnv()))
   263  	if err != nil {
   264  		if runtime.ErrorIsUnexpectedEOF(err) {
   265  			return true, nil
   266  		}
   267  		b.buf.AppendLine(edit.NewLineFromString(err.Error(), luaLineData{index: b.currentIndex, tp: luaParseError}))
   268  		return false, err
   269  	}
   270  
   271  	term := runtime.NewTerminationWith(nil, 0, true)
   272  	rtErr := runtime.Call(b.r.MainThread(), runtime.FunctionValue(clos), nil, term)
   273  
   274  	// Print stdout
   275  	meta := luaLineData{index: b.currentIndex, tp: luaStdout}
   276  	for {
   277  		line, err := b.stdout.ReadString('\n')
   278  		if err != nil {
   279  			break
   280  		}
   281  		b.buf.AppendLine(edit.NewLineFromString(line[:len(line)-1], meta))
   282  	}
   283  
   284  	// Print runtime error
   285  	if rtErr != nil {
   286  		meta.tp = luaRuntimeError
   287  		for _, s := range strings.Split(rtErr.Error(), "\n") {
   288  			b.buf.AppendLine(edit.NewLineFromString("! "+s, meta))
   289  		}
   290  	}
   291  
   292  	// Print result
   293  	meta.tp = luaOutput
   294  	for i, x := range term.Etc() {
   295  		b.r.SetEnv(b.r.GlobalEnv(), fmt.Sprintf("_%d", i+1), x)
   296  		if i == 0 {
   297  			b.r.SetEnv(b.r.GlobalEnv(), "_", x)
   298  		}
   299  		b.buf.AppendLine(edit.NewLineFromString("= "+quoteLuaVal(x), meta))
   300  	}
   301  
   302  	b.currentIndex++
   303  	b.buf.AppendLine(edit.NewLineFromString(inputName(b.currentIndex), luaLineData{index: b.currentIndex, tp: luaComment}))
   304  	b.buf.AppendLine(edit.NewLineFromString("> ", luaLineData{index: b.currentIndex, tp: luaInput}))
   305  	return false, nil
   306  }
   307  
   308  func (b *LuaBuffer) IsCurrentLast(l int) bool {
   309  	line, err := b.getEditableLine(l, -1)
   310  	if err != nil || line.Len() > 2 {
   311  		return false
   312  	}
   313  	_, err = b.getEditableLine(l+1, -1)
   314  	return err != nil
   315  }
   316  
   317  func (b *LuaBuffer) IsEndOfCurrentInput(l, c int) bool {
   318  	line, err := b.getEditableLine(l, c)
   319  	if err != nil || c < line.Len() {
   320  		return false
   321  	}
   322  	_, err = b.getEditableLine(l+1, -1)
   323  	return err != nil
   324  }
   325  
   326  func (b *LuaBuffer) deleteCurrent() {
   327  	l := b.buf.LineCount()
   328  	for ; l > 0; l-- {
   329  		data := b.buf.Line(l - 1).Meta.(luaLineData)
   330  		if data.index != b.currentIndex || (data.tp != luaParseError && data.tp != luaInput) {
   331  			break
   332  		}
   333  	}
   334  	b.buf.Truncate(l)
   335  }
   336  
   337  func (b *LuaBuffer) ResetCurrent() {
   338  	b.deleteCurrent()
   339  	b.buf.AppendLine(edit.NewLineFromString("> ", luaLineData{index: b.currentIndex, tp: luaInput}))
   340  }
   341  
   342  func (b *LuaBuffer) CopyToCurrent(l int) error {
   343  	if l < 0 || l >= b.LineCount() {
   344  		return fmt.Errorf("out of range")
   345  	}
   346  	line := b.buf.Line(l)
   347  	meta := line.Meta.(luaLineData)
   348  	if meta.tp != luaInput {
   349  		return fmt.Errorf("can only copy input")
   350  	}
   351  	if meta.index == b.currentIndex {
   352  		return fmt.Errorf("can only copy previous input")
   353  	}
   354  	for ; l > 0; l-- {
   355  		prevMeta := b.buf.Line(l - 1).Meta.(luaLineData)
   356  		if prevMeta != meta {
   357  			break
   358  		}
   359  	}
   360  	b.deleteCurrent()
   361  	currentMeta := luaLineData{index: b.currentIndex, tp: luaInput}
   362  	for {
   363  		line := b.buf.Line(l)
   364  		if line.Meta.(luaLineData) != meta {
   365  			return nil
   366  		}
   367  		b.buf.AppendLine(edit.NewLineFromString(line.String(), currentMeta))
   368  		l++
   369  	}
   370  }
   371  
   372  func quoteLuaVal(v runtime.Value) string {
   373  	s, ok := v.TryString()
   374  	if ok {
   375  		var q byte = '"'
   376  		if strings.ContainsRune(s, '"') {
   377  			q = '\''
   378  		}
   379  		return luastrings.Quote(s, q)
   380  	}
   381  	s, _ = v.ToString()
   382  	return s
   383  }
   384  
   385  func inputName(n int) string {
   386  	return fmt.Sprintf("[%d]", n+1)
   387  }