262 lines
5.2 KiB
Go
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
|
|
}
|