277 lines
5.9 KiB
Go
277 lines
5.9 KiB
Go
package repl
|
|
|
|
// Package repl implements the Read-Eval-Print-Loop or interactive console
|
|
// by lexing, parsing and evaluating the input in the interpreter
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"monkey/compiler"
|
|
"monkey/evaluator"
|
|
"monkey/lexer"
|
|
"monkey/object"
|
|
"monkey/parser"
|
|
"monkey/vm"
|
|
"os"
|
|
)
|
|
|
|
// PROMPT is the REPL prompt displayed for each input
|
|
const PROMPT = ">> "
|
|
|
|
// MonkeyFace is the REPL's face of shock and horror when you encounter a
|
|
// parser error :D
|
|
const MonkeyFace = ` __,__
|
|
.--. .-" "-. .--.
|
|
/ .. \/ .-. .-. \/ .. \
|
|
| | '| / Y \ |' | |
|
|
| \ \ \ 0 | 0 / / / |
|
|
\ '- ,\.-"""""""-./, -' /
|
|
''-' /_ ^ ^ _\ '-''
|
|
| \._ _./ |
|
|
\ \ '~' / /
|
|
'._ '-=-' _.'
|
|
'-----'
|
|
`
|
|
|
|
type Options struct {
|
|
Debug bool
|
|
Engine string
|
|
Interactive bool
|
|
}
|
|
|
|
type VMState struct {
|
|
constants []object.Object
|
|
globals []object.Object
|
|
symbols *compiler.SymbolTable
|
|
}
|
|
|
|
func NewVMState() *VMState {
|
|
symbolTable := compiler.NewSymbolTable()
|
|
for i, builtin := range object.BuiltinsIndex {
|
|
symbolTable.DefineBuiltin(i, builtin.Name)
|
|
}
|
|
|
|
return &VMState{
|
|
constants: []object.Object{},
|
|
globals: make([]object.Object, vm.GlobalsSize),
|
|
symbols: symbolTable,
|
|
}
|
|
}
|
|
|
|
type REPL struct {
|
|
user string
|
|
args []string
|
|
opts *Options
|
|
}
|
|
|
|
func New(user string, args []string, opts *Options) *REPL {
|
|
object.StandardInput = os.Stdin
|
|
object.StandardOutput = os.Stdout
|
|
object.ExitFunction = os.Exit
|
|
|
|
return &REPL{user, args, opts}
|
|
}
|
|
|
|
// Eval parses and evalulates the program given by f and returns the resulting
|
|
// environment, any errors are printed to stderr
|
|
func (r *REPL) Eval(f io.Reader) (env *object.Environment) {
|
|
env = object.NewEnvironment()
|
|
|
|
b, err := io.ReadAll(f)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
|
|
return
|
|
}
|
|
|
|
l := lexer.New(string(b))
|
|
p := parser.New(l)
|
|
|
|
program := p.ParseProgram()
|
|
if len(p.Errors()) != 0 {
|
|
printParserErrors(os.Stderr, p.Errors())
|
|
return
|
|
}
|
|
|
|
evaluator.Eval(program, env)
|
|
return
|
|
}
|
|
|
|
// Exec parses, compiles and executes the program given by f and returns
|
|
// the resulting virtual machine, any errors are printed to stderr
|
|
func (r *REPL) Exec(f io.Reader) (state *VMState) {
|
|
b, err := io.ReadAll(f)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
|
|
return
|
|
}
|
|
|
|
state = NewVMState()
|
|
|
|
l := lexer.New(string(b))
|
|
p := parser.New(l)
|
|
|
|
program := p.ParseProgram()
|
|
if len(p.Errors()) != 0 {
|
|
printParserErrors(os.Stderr, p.Errors())
|
|
return
|
|
}
|
|
|
|
c := compiler.NewWithState(state.symbols, state.constants)
|
|
c.Debug = r.opts.Debug
|
|
err = c.Compile(program)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
|
|
return
|
|
}
|
|
|
|
code := c.Bytecode()
|
|
state.constants = code.Constants
|
|
|
|
machine := vm.NewWithGlobalState(code, state.globals)
|
|
machine.Debug = r.opts.Debug
|
|
err = machine.Run()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// StartEvalLoop starts the REPL in a continious eval loop
|
|
func (r *REPL) StartEvalLoop(in io.Reader, out io.Writer, env *object.Environment) {
|
|
scanner := bufio.NewScanner(in)
|
|
|
|
if env == nil {
|
|
env = object.NewEnvironment()
|
|
}
|
|
|
|
for {
|
|
fmt.Printf(PROMPT)
|
|
scanned := scanner.Scan()
|
|
if !scanned {
|
|
return
|
|
}
|
|
|
|
line := scanner.Text()
|
|
|
|
l := lexer.New(line)
|
|
p := parser.New(l)
|
|
|
|
program := p.ParseProgram()
|
|
if len(p.Errors()) != 0 {
|
|
printParserErrors(out, p.Errors())
|
|
continue
|
|
}
|
|
|
|
obj := evaluator.Eval(program, env)
|
|
if _, ok := obj.(*object.Null); !ok {
|
|
io.WriteString(out, obj.Inspect())
|
|
io.WriteString(out, "\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
// StartExecLoop starts the REPL in a continious exec loop
|
|
func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *VMState) {
|
|
scanner := bufio.NewScanner(in)
|
|
|
|
if state == nil {
|
|
state = NewVMState()
|
|
}
|
|
|
|
for {
|
|
fmt.Printf(PROMPT)
|
|
scanned := scanner.Scan()
|
|
if !scanned {
|
|
return
|
|
}
|
|
|
|
line := scanner.Text()
|
|
|
|
l := lexer.New(line)
|
|
p := parser.New(l)
|
|
|
|
program := p.ParseProgram()
|
|
if len(p.Errors()) != 0 {
|
|
printParserErrors(out, p.Errors())
|
|
continue
|
|
}
|
|
|
|
c := compiler.NewWithState(state.symbols, state.constants)
|
|
c.Debug = r.opts.Debug
|
|
err := c.Compile(program)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
|
|
return
|
|
}
|
|
|
|
code := c.Bytecode()
|
|
state.constants = code.Constants
|
|
|
|
machine := vm.NewWithGlobalState(code, state.globals)
|
|
machine.Debug = r.opts.Debug
|
|
err = machine.Run()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
|
|
return
|
|
}
|
|
|
|
obj := machine.LastPoppedStackElem()
|
|
if _, ok := obj.(*object.Null); !ok {
|
|
io.WriteString(out, obj.Inspect())
|
|
io.WriteString(out, "\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *REPL) Run() {
|
|
object.Arguments = make([]string, len(r.args))
|
|
copy(object.Arguments, r.args)
|
|
|
|
if len(r.args) == 0 {
|
|
fmt.Printf("Hello %s! This is the Monkey programming language!\n", r.user)
|
|
fmt.Printf("Feel free to type in commands\n")
|
|
if r.opts.Engine == "eval" {
|
|
r.StartEvalLoop(os.Stdin, os.Stdout, nil)
|
|
} else {
|
|
r.StartExecLoop(os.Stdin, os.Stdout, nil)
|
|
}
|
|
return
|
|
}
|
|
|
|
if len(r.args) > 0 {
|
|
f, err := os.Open(r.args[0])
|
|
if err != nil {
|
|
log.Fatalf("could not open source file %s: %s", r.args[0], err)
|
|
}
|
|
|
|
// Remove program argument (zero)
|
|
r.args = r.args[1:]
|
|
object.Arguments = object.Arguments[1:]
|
|
|
|
if r.opts.Engine == "eval" {
|
|
env := r.Eval(f)
|
|
if r.opts.Interactive {
|
|
r.StartEvalLoop(os.Stdin, os.Stdout, env)
|
|
}
|
|
} else {
|
|
state := r.Exec(f)
|
|
if r.opts.Interactive {
|
|
r.StartExecLoop(os.Stdin, os.Stdout, state)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func printParserErrors(out io.Writer, errors []string) {
|
|
io.WriteString(out, MonkeyFace)
|
|
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
|
|
io.WriteString(out, " parser errors:\n")
|
|
for _, msg := range errors {
|
|
io.WriteString(out, "\t"+msg+"\n")
|
|
}
|
|
}
|