Files
monkey/repl.go
Chuck Smith 4c9ec5aaaa
Some checks failed
Build / build (push) Failing after 6m4s
Test / build (push) Failing after 6m33s
server
2024-04-02 12:21:41 -04:00

262 lines
5.2 KiB
Go

package monkey
import (
"bufio"
"fmt"
"github.com/tebeka/atexit"
"io"
"log"
"monkey/internal/context"
"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(args []string, opts *Options) error {
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
ctx := context.New(
context.WithArgs(args),
context.WithStdout(t),
context.WithStderr(t),
context.WithExit(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())
}
opts := []vm.Option{
vm.WithContext(ctx),
vm.WithDebug(opts.Debug),
vm.WithTrace(opts.Trace),
vm.WithState(state),
}
mvm := vm.New("<stdin>", c.Bytecode(), opts...)
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(args []string, opts *Options) {
var 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
ctx := context.New(
context.WithArgs(args),
context.WithStdout(t),
context.WithStderr(t),
context.WithExit(atexit.Exit),
)
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())
}
opts := []vm.Option{
vm.WithContext(ctx),
vm.WithDebug(opts.Debug),
vm.WithTrace(opts.Trace),
vm.WithState(state),
}
mvm := vm.New("<stdin>", c.Bytecode(), opts...)
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
}