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 }