refactor how repl works
This commit is contained in:
191
repl.go
Normal file
191
repl.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package monkey
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"monkey/internal/compiler"
|
||||
"monkey/internal/object"
|
||||
"monkey/internal/parser"
|
||||
"monkey/internal/vm"
|
||||
)
|
||||
|
||||
// Package repl implements the Read-Eval-Print-Loop or interactive console
|
||||
// by lexing, parsing and evaluating the input in the interpreter
|
||||
|
||||
func VmREPL() error {
|
||||
var state = vm.NewVMState()
|
||||
|
||||
initState, err := term.MakeRaw(0)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return fmt.Errorf("error opening terminal: %w", err)
|
||||
}
|
||||
defer term.Restore(0, initState)
|
||||
|
||||
t := term.NewTerminal(os.Stdin, ">>> ")
|
||||
t.AutoCompleteCallback = autoComplete
|
||||
object.Stdout = t
|
||||
|
||||
PrintVersionInfo(t)
|
||||
for {
|
||||
input, err := t.ReadLine()
|
||||
check(t, initState, err)
|
||||
|
||||
input = strings.TrimRight(input, " ")
|
||||
if len(input) > 0 && input[len(input)-1] == '{' {
|
||||
input, err = acceptUntil(t, input, "\n\n")
|
||||
check(t, initState, err)
|
||||
}
|
||||
|
||||
res, errs := parser.Parse("<stdin>", input)
|
||||
if len(errs) != 0 {
|
||||
for _, e := range errs {
|
||||
fmt.Fprintln(t, e)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
c := compiler.NewWithState(state.Symbols, &state.Constants)
|
||||
if err := c.Compile(res); err != nil {
|
||||
fmt.Fprintln(t, err)
|
||||
continue
|
||||
}
|
||||
|
||||
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
|
||||
if err := mvm.Run(); err != nil {
|
||||
fmt.Fprintf(t, "runtime error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if val := mvm.LastPoppedStackElem(); val.Type() != object.NULL_OBJ {
|
||||
fmt.Fprintln(t, val.Inspect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func autoComplete(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||
if key == '\t' {
|
||||
return line + " ", pos + 4, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func check(t *term.Terminal, initState *term.State, err error) {
|
||||
if err != nil {
|
||||
// Quit without error on Ctrl^D.
|
||||
if err != io.EOF {
|
||||
fmt.Fprintln(t, err)
|
||||
}
|
||||
term.Restore(0, initState)
|
||||
fmt.Println()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptUntil(t *term.Terminal, start, end string) (string, error) {
|
||||
var buf strings.Builder
|
||||
|
||||
buf.WriteString(start)
|
||||
buf.WriteRune('\n')
|
||||
t.SetPrompt("... ")
|
||||
defer t.SetPrompt(">>> ")
|
||||
|
||||
for {
|
||||
line, err := t.ReadLine()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
line = strings.TrimRight(line, " ")
|
||||
buf.WriteString(line)
|
||||
buf.WriteRune('\n')
|
||||
|
||||
if s := buf.String(); len(s) > len(end) && s[len(s)-len(end):] == end {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func SimpleVmREPL() {
|
||||
var (
|
||||
state = vm.NewVMState()
|
||||
reader = bufio.NewReader(os.Stdin)
|
||||
)
|
||||
|
||||
PrintVersionInfo(os.Stdout)
|
||||
for {
|
||||
fmt.Print(">>> ")
|
||||
input, err := reader.ReadString('\n')
|
||||
simpleCheck(err)
|
||||
|
||||
input = strings.TrimRight(input, " \n")
|
||||
if len(input) > 0 && input[len(input)-1] == '{' {
|
||||
input, err = simpleAcceptUntil(reader, input, "\n\n")
|
||||
simpleCheck(err)
|
||||
}
|
||||
|
||||
res, errs := parser.Parse("<stdin>", input)
|
||||
if len(errs) != 0 {
|
||||
for _, e := range errs {
|
||||
fmt.Println(e)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
c := compiler.NewWithState(state.Symbols, &state.Constants)
|
||||
c.SetFileInfo("<stdin>", input)
|
||||
if err := c.Compile(res); err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
|
||||
|
||||
if err := mvm.Run(); err != nil {
|
||||
fmt.Printf("runtime error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if val := mvm.LastPoppedStackElem(); val.Type() != object.NULL_OBJ {
|
||||
fmt.Println(val.Inspect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func simpleCheck(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func simpleAcceptUntil(r *bufio.Reader, start, end string) (string, error) {
|
||||
var buf strings.Builder
|
||||
|
||||
buf.WriteString(start)
|
||||
buf.WriteRune('\n')
|
||||
for {
|
||||
fmt.Print("... ")
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
line = strings.TrimRight(line, " \n")
|
||||
buf.WriteString(line)
|
||||
buf.WriteRune('\n')
|
||||
|
||||
if s := buf.String(); len(s) > len(end) && s[len(s)-len(end):] == end {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user