Files
monkey/repl.go
Chuck Smith 07fd82b261
Some checks failed
Build / build (push) Successful in 10m29s
Publish Image / publish (push) Failing after 31s
Test / build (push) Failing after 6m34s
optimizations
2024-04-02 14:08:08 -04:00

242 lines
4.7 KiB
Go

package monkey
import (
"bufio"
"fmt"
"github.com/tebeka/atexit"
"io"
"log"
"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
// REPL provides a read-eval-print loop for the monkey virtual machine.
func REPL(opts *Options) error {
if opts == nil {
opts = &Options{}
}
initState, err := term.MakeRaw(0)
if err != nil {
fmt.Println(err)
return fmt.Errorf("error opening terminal: %w", err)
}
defer func() {
if err := recover(); err != nil {
log.Printf("recovered from panic: %s", err)
}
term.Restore(0, initState)
}()
atexit.Register(func() {
term.Restore(0, initState)
})
t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete
opts.Exit = atexit.Exit
state := vm.NewState()
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
}
if opts.Debug {
log.Printf("AST:\n%s\n", res)
}
c := compiler.NewWithState(state.Symbols, &state.Constants)
c.Debug = opts.Debug
c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil {
fmt.Fprintln(t, err)
continue
}
if opts.Debug {
log.Printf("Bytecode:\n%s\n", c.Bytecode())
}
mvm := vm.New("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
if err := mvm.Run(); err != nil {
fmt.Fprintf(t, "runtime error: %v\n", err)
continue
}
if val := mvm.LastPoppedStackElem(); val != nil && val.Type() != object.NullType {
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
}
// SimpleREPL provides a simple read-eval-print loop for the monkey virtual machine.
func SimpleREPL(opts *Options) {
if opts == nil {
opts = &Options{}
}
reader := bufio.NewReader(os.Stdin)
defer func() {
if err := recover(); err != nil {
log.Printf("recovered from panic: %s", err)
}
}()
t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete
state := vm.NewState()
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
}
if opts.Debug {
log.Printf("AST:\n%s\n", res)
}
c := compiler.NewWithState(state.Symbols, &state.Constants)
c.Debug = opts.Debug
c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil {
fmt.Println(err)
continue
}
if opts.Debug {
log.Printf("Bytecode:\n%s\n", c.Bytecode())
}
mvm := vm.New("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
if err := mvm.Run(); err != nil {
fmt.Printf("runtime error: %v\n", err)
continue
}
if val := mvm.LastPoppedStackElem(); val != nil && val.Type() != object.NullType {
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
}