restructure project
This commit is contained in:
257
internal/repl/repl.go
Normal file
257
internal/repl/repl.go
Normal file
@@ -0,0 +1,257 @@
|
||||
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/internal/compiler"
|
||||
"monkey/internal/evaluator"
|
||||
"monkey/internal/lexer"
|
||||
"monkey/internal/object"
|
||||
"monkey/internal/parser"
|
||||
"monkey/internal/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 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 *vm.VMState) {
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
state = vm.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.NewWithState(code, state)
|
||||
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 *vm.VMState) {
|
||||
scanner := bufio.NewScanner(in)
|
||||
|
||||
if state == nil {
|
||||
state = vm.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.NewWithState(code, state)
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user