refactor how repl works
Some checks failed
Build / build (push) Failing after 5m26s
Publish Image / publish (push) Failing after 45s
Test / build (push) Failing after 5m54s

This commit is contained in:
Chuck Smith
2024-03-28 16:51:54 -04:00
parent fc6ceee02c
commit 244b71d245
32 changed files with 612 additions and 476 deletions

View File

@@ -1,137 +1,38 @@
// Package main is the main entrypoint for the monkey interpreter
package main package main
import ( import (
"flag" "flag"
"fmt" "monkey"
"io"
"log"
"os" "os"
"os/user"
"path"
"strings"
"github.com/pkg/profile"
"monkey/internal"
"monkey/internal/compiler"
"monkey/internal/lexer"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/repl"
) )
var (
engine string
interactive bool
compile bool
version bool
debug bool
profileCPU bool
profileMem bool
)
func init() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] [<filename>]\n", path.Base(os.Args[0]))
flag.PrintDefaults()
os.Exit(0)
}
flag.BoolVar(&version, "v", false, "display version information")
flag.BoolVar(&debug, "d", false, "enable debug mode")
flag.BoolVar(&compile, "c", false, "compile input to bytecode")
flag.BoolVar(&interactive, "i", false, "enable interactive mode")
flag.StringVar(&engine, "e", "vm", "engine to use (eval or vm)")
flag.BoolVar(&profileCPU, "profile-cpu", false, "Enable CPU profiling.")
flag.BoolVar(&profileMem, "profile-mem", false, "Enable Memory profiling.")
}
// Indent indents a block of text with an indent string
func Indent(text, indent string) string {
if text[len(text)-1:] == "\n" {
result := ""
for _, j := range strings.Split(text[:len(text)-1], "\n") {
result += indent + j + "\n"
}
return result
}
result := ""
for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
result += indent + j + "\n"
}
return result[:len(result)-1]
}
func main() { func main() {
var (
compile bool
version bool
simple bool
)
flag.BoolVar(&compile, "c", false, "Compile a monkey file into a '.monkeyc' bytecode file.")
flag.BoolVar(&simple, "s", false, "Use simple REPL instead of opening a terminal.")
flag.BoolVar(&version, "v", false, "Print Monkey version information.")
flag.Parse() flag.Parse()
if version { switch {
fmt.Printf("%s %s", path.Base(os.Args[0]), internal.FullVersion()) case compile:
os.Exit(0) monkey.CompileFiles(flag.Args())
}
user, err := user.Current() case version:
if err != nil { monkey.PrintVersionInfo(os.Stdout)
log.Fatalf("could not determine current user: %s", err)
}
args := flag.Args() case flag.NArg() > 0:
monkey.ExecFileVM(flag.Arg(0))
if profileCPU { case simple:
defer profile.Start(profile.CPUProfile).Stop() monkey.SimpleVmREPL()
} else if profileMem {
defer profile.Start(profile.MemProfile).Stop()
}
if compile { default:
if len(args) < 1 { monkey.VmREPL()
log.Fatal("no source file given to compile")
}
f, err := os.Open(args[0])
if err != nil {
log.Fatal(err)
}
defer f.Close()
b, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}
l := lexer.New(string(b))
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
log.Fatal(p.Errors())
}
c := compiler.New()
err = c.Compile(program)
if err != nil {
log.Fatal(err)
}
code := c.Bytecode()
fmt.Printf("Main:\n%s\n", code.Instructions)
fmt.Print("Constants:\n")
for i, constant := range code.Constants {
fmt.Printf("%04d %s\n", i, constant.Inspect())
if fn, ok := constant.(*object.CompiledFunction); ok {
fmt.Printf("%s\n", Indent(fn.Instructions.String(), " "))
}
}
} else {
opts := &repl.Options{
Debug: debug,
Engine: engine,
Interactive: interactive,
}
repl := repl.New(user.Username, args, opts)
repl.Run()
} }
} }

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.21
require ( require (
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/pkg/profile v1.7.0 github.com/pkg/profile v1.7.0
golang.org/x/term v0.15.0
) )
require ( require (

2
go.sum
View File

@@ -17,6 +17,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -6,7 +6,7 @@ import (
) )
// Args ... // Args ...
func Args(args ...object.Object) object.Object { func args(args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"args", args, "args", args,
typing.ExactArgs(0), typing.ExactArgs(0),
@@ -14,8 +14,8 @@ func Args(args ...object.Object) object.Object {
return newError(err.Error()) return newError(err.Error())
} }
elements := make([]object.Object, len(object.Arguments)) elements := make([]object.Object, len(object.Args))
for i, arg := range object.Arguments { for i, arg := range object.Args {
elements[i] = &object.String{Value: arg} elements[i] = &object.String{Value: arg}
} }
return &object.Array{Elements: elements} return &object.Array{Elements: elements}

View File

@@ -22,7 +22,7 @@ var Builtins = map[string]*object.Builtin{
"int": {Name: "int", Fn: Int}, "int": {Name: "int", Fn: Int},
"str": {Name: "str", Fn: Str}, "str": {Name: "str", Fn: Str},
"type": {Name: "type", Fn: TypeOf}, "type": {Name: "type", Fn: TypeOf},
"args": {Name: "args", Fn: Args}, "args": {Name: "args", Fn: args},
"lower": {Name: "lower", Fn: Lower}, "lower": {Name: "lower", Fn: Lower},
"upper": {Name: "upper", Fn: Upper}, "upper": {Name: "upper", Fn: Upper},
"join": {Name: "join", Fn: Join}, "join": {Name: "join", Fn: Join},

View File

@@ -0,0 +1,147 @@
package compiler
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"monkey/internal/code"
"monkey/internal/object"
)
type Bytecode struct {
Instructions code.Instructions
Constants []object.Object
}
type encoder struct {
bytes.Buffer
}
func (e *encoder) Write(b []byte) (err error) {
_, err = e.Buffer.Write(b)
return
}
func (e *encoder) WriteString(s string) (err error) {
_, err = e.Buffer.WriteString(s)
return
}
func (e *encoder) WriteValue(data any) error {
return binary.Write(&e.Buffer, binary.BigEndian, data)
}
func (e *encoder) WriteObjects(objs ...object.Object) (err error) {
for _, obj := range objs {
err = errors.Join(err, e.WriteValue(len(string(obj.Type()))))
err = errors.Join(err, e.WriteString(string(obj.Type())))
switch o := obj.(type) {
case *object.Null:
break
case *object.Boolean:
err = errors.Join(err, e.WriteValue(o.Value))
case *object.Integer:
err = errors.Join(err, e.WriteValue(o.Value))
case *object.String:
err = errors.Join(err, e.WriteValue(len(o.Value)))
err = errors.Join(err, e.WriteValue(o.Value))
case *object.CompiledFunction:
err = errors.Join(err, e.WriteValue(o.NumParameters))
err = errors.Join(err, e.WriteValue(o.NumLocals))
err = errors.Join(err, e.WriteValue(len(o.Instructions)))
err = errors.Join(err, e.Write(o.Instructions))
}
}
return
}
func (b Bytecode) Encode() (data []byte, err error) {
var e encoder
err = errors.Join(err, e.WriteValue(len(b.Instructions)))
err = errors.Join(err, e.Write(b.Instructions))
err = errors.Join(err, e.WriteValue(len(b.Constants)))
err = errors.Join(err, e.WriteObjects(b.Constants...))
return e.Bytes(), err
}
type decoder struct {
pos int
b []byte
}
func (d *decoder) Byte() (b byte) {
b = d.b[d.pos]
d.pos++
return
}
func (d *decoder) Int() (i int) {
i = int(binary.BigEndian.Uint32(d.b[d.pos:]))
d.pos += 4
return
}
func (d *decoder) Uint64() (i uint64) {
i = binary.BigEndian.Uint64(d.b[d.pos:])
d.pos += 8
return
}
func (d *decoder) Int64() (i int64) {
return int64(d.Uint64())
}
func (d *decoder) Float64() (f float64) {
return math.Float64frombits(d.Uint64())
}
func (d *decoder) Bytes(len int) (b []byte) {
b = d.b[d.pos : d.pos+len]
d.pos += len
return
}
func (d *decoder) String(len int) (s string) {
s = string(d.b[d.pos : d.pos+len])
d.pos += len
return
}
func (d *decoder) Objects(len int) (o []object.Object) {
for i := 0; i < len; i++ {
switch t := d.String(d.Int()); t {
case object.NULL_OBJ:
o = append(o, &object.Null{})
case object.BOOLEAN_OBJ:
o = append(o, &object.Boolean{Value: d.Byte() == 1})
case object.INTEGER_OBJ:
o = append(o, &object.Integer{Value: d.Int64()})
case object.STRING_OBJ:
o = append(o, &object.String{Value: d.String(d.Int())})
case object.COMPILED_FUNCTION_OBJ:
// The order of the fields has to reflect the data layout in the encoded bytecode.
o = append(o, &object.CompiledFunction{
NumParameters: d.Int(),
NumLocals: d.Int(),
Instructions: d.Bytes(d.Int()),
})
default:
panic(fmt.Sprintf("decoder: unsupported decoding for type %s", t))
}
}
return
}
func Decode(b []byte) *Bytecode {
var d = decoder{b: b}
// The order of the fields has to reflect the data layout in the encoded bytecode.
return &Bytecode{
Instructions: d.Bytes(d.Int()),
Constants: d.Objects(d.Int()),
}
}

View File

@@ -25,8 +25,11 @@ type CompilationScope struct {
type Compiler struct { type Compiler struct {
Debug bool Debug bool
fn string
input string
l int l int
constants []object.Object constants *[]object.Object
symbolTable *SymbolTable symbolTable *SymbolTable
@@ -48,7 +51,7 @@ func New() *Compiler {
} }
return &Compiler{ return &Compiler{
constants: []object.Object{}, constants: &[]object.Object{},
symbolTable: symbolTable, symbolTable: symbolTable,
scopes: []CompilationScope{mainScope}, scopes: []CompilationScope{mainScope},
scopeIndex: 0, scopeIndex: 0,
@@ -59,7 +62,7 @@ func (c *Compiler) currentInstructions() code.Instructions {
return c.scopes[c.scopeIndex].instructions return c.scopes[c.scopeIndex].instructions
} }
func NewWithState(s *SymbolTable, constants []object.Object) *Compiler { func NewWithState(s *SymbolTable, constants *[]object.Object) *Compiler {
compiler := New() compiler := New()
compiler.symbolTable = s compiler.symbolTable = s
compiler.constants = constants compiler.constants = constants
@@ -442,7 +445,7 @@ func (c *Compiler) Compile(node ast.Node) error {
} }
freeSymbols := c.symbolTable.FreeSymbols freeSymbols := c.symbolTable.FreeSymbols
numLocals := c.symbolTable.numDefinitions numLocals := c.symbolTable.NumDefinitions
instructions := c.leaveScope() instructions := c.leaveScope()
for _, s := range freeSymbols { for _, s := range freeSymbols {
@@ -529,8 +532,8 @@ func (c *Compiler) Compile(node ast.Node) error {
} }
func (c *Compiler) addConstant(obj object.Object) int { func (c *Compiler) addConstant(obj object.Object) int {
c.constants = append(c.constants, obj) *c.constants = append(*c.constants, obj)
return len(c.constants) - 1 return len(*c.constants) - 1
} }
func (c *Compiler) emit(op code.Opcode, operands ...int) int { func (c *Compiler) emit(op code.Opcode, operands ...int) int {
@@ -542,6 +545,11 @@ func (c *Compiler) emit(op code.Opcode, operands ...int) int {
return pos return pos
} }
func (c *Compiler) SetFileInfo(fn, input string) {
c.fn = fn
c.input = input
}
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
previous := c.scopes[c.scopeIndex].lastInstruction previous := c.scopes[c.scopeIndex].lastInstruction
last := EmittedInstruction{Opcode: op, Position: pos} last := EmittedInstruction{Opcode: op, Position: pos}
@@ -553,7 +561,7 @@ func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
func (c *Compiler) Bytecode() *Bytecode { func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{ return &Bytecode{
Instructions: c.currentInstructions(), Instructions: c.currentInstructions(),
Constants: c.constants, Constants: *c.constants,
} }
} }
@@ -630,11 +638,6 @@ func (c *Compiler) replaceLastPopWithReturn() {
c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturn c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturn
} }
type Bytecode struct {
Instructions code.Instructions
Constants []object.Object
}
func (c *Compiler) loadSymbol(s Symbol) { func (c *Compiler) loadSymbol(s Symbol) {
switch s.Scope { switch s.Scope {
case GlobalScope: case GlobalScope:

View File

@@ -1090,7 +1090,7 @@ func runCompilerTests2(t *testing.T, tests []compilerTestCase2) {
func parse(input string) *ast.Program { func parse(input string) *ast.Program {
l := lexer.New(input) l := lexer.New(input)
p := parser.New(l) p := parser.New("<text", l)
return p.ParseProgram() return p.ParseProgram()
} }

View File

@@ -20,7 +20,7 @@ type SymbolTable struct {
Outer *SymbolTable Outer *SymbolTable
Store map[string]Symbol Store map[string]Symbol
numDefinitions int NumDefinitions int
FreeSymbols []Symbol FreeSymbols []Symbol
} }
@@ -38,7 +38,7 @@ func NewSymbolTable() *SymbolTable {
} }
func (s *SymbolTable) Define(name string) Symbol { func (s *SymbolTable) Define(name string) Symbol {
symbol := Symbol{Name: name, Index: s.numDefinitions} symbol := Symbol{Name: name, Index: s.NumDefinitions}
if s.Outer == nil { if s.Outer == nil {
symbol.Scope = GlobalScope symbol.Scope = GlobalScope
} else { } else {
@@ -46,7 +46,7 @@ func (s *SymbolTable) Define(name string) Symbol {
} }
s.Store[name] = symbol s.Store[name] = symbol
s.numDefinitions++ s.NumDefinitions++
return symbol return symbol
} }

View File

@@ -518,7 +518,7 @@ func EvalModule(name string) object.Object {
} }
l := lexer.New(string(b)) l := lexer.New(string(b))
p := parser.New(l) p := parser.New(fmt.Sprintf("<module %s>", name), l)
module := p.ParseProgram() module := p.ParseProgram()
if len(p.Errors()) != 0 { if len(p.Errors()) != 0 {

View File

@@ -796,7 +796,7 @@ func TestWhileExpressions(t *testing.T) {
func testEval(input string) object.Object { func testEval(input string) object.Object {
l := lexer.New(input) l := lexer.New(input)
p := parser.New(l) p := parser.New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
env := object.NewEnvironment() env := object.NewEnvironment()
@@ -862,9 +862,9 @@ func TestImportExpressions(t *testing.T) {
input string input string
expected interface{} expected interface{}
}{ }{
{`mod := import("../../testdata/mod"); mod.A`, 5}, {`mod := import("../testdata/mod"); mod.A`, 5},
{`mod := import("../../testdata/mod"); mod.Sum(2, 3)`, 5}, {`mod := import("../testdata/mod"); mod.Sum(2, 3)`, 5},
{`mod := import("../../testdata/mod"); mod.a`, nil}, {`mod := import("../testdata/mod"); mod.a`, nil},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -1,10 +1,14 @@
package object package object
import "io" import (
"io"
"os"
)
var ( var (
Arguments []string Args []string = os.Args
StandardInput io.Reader Stdin io.Reader = os.Stdin
StandardOutput io.Writer Stdout io.Writer = os.Stdout
ExitFunction func(int) Stderr io.Writer = os.Stderr
ExitFunction func(int) = os.Exit
) )

View File

@@ -61,6 +61,7 @@ type (
) )
type Parser struct { type Parser struct {
fn string
l *lexer.Lexer l *lexer.Lexer
errors []string errors []string
@@ -71,8 +72,9 @@ type Parser struct {
infixParseFns map[token.TokenType]infixParseFn infixParseFns map[token.TokenType]infixParseFn
} }
func New(l *lexer.Lexer) *Parser { func New(fn string, l *lexer.Lexer) *Parser {
p := &Parser{ p := &Parser{
fn: fn,
l: l, l: l,
errors: []string{}, errors: []string{},
} }
@@ -619,3 +621,11 @@ func (p *Parser) parseImportExpression() ast.Expression {
return expression return expression
} }
// Parse parses the input source into a top-level AST for either evaluation
// or compilation to bytecode. The parameter fn denotes the filename the source
// originated from.
func Parse(fn, input string) (prog ast.Node, errors []string) {
p := New(fn, lexer.New(input))
return p.ParseProgram(), p.Errors()
}

View File

@@ -20,7 +20,7 @@ func TestBindExpressions(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -40,7 +40,7 @@ func TestReturnStatements(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -68,7 +68,7 @@ func TestIdentifierExpression(t *testing.T) {
input := "foobar;" input := "foobar;"
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -99,7 +99,7 @@ func TestIntegerLiteralExpression(t *testing.T) {
input := "5;" input := "5;"
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -143,7 +143,7 @@ func TestParsingPrefixExpressions(t *testing.T) {
for _, tt := range prefixTests { for _, tt := range prefixTests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -202,7 +202,7 @@ func TestParsingInfixExpressions(t *testing.T) {
for _, tt := range infixTests { for _, tt := range infixTests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -353,7 +353,7 @@ func TestOperatorPrecedenceParsing(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -375,7 +375,7 @@ func TestBooleanExpression(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -405,7 +405,7 @@ func TestIfExpression(t *testing.T) {
input := `if (x < y) { x }` input := `if (x < y) { x }`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -452,7 +452,7 @@ func TestIfExpression(t *testing.T) {
func TestNullExpression(t *testing.T) { func TestNullExpression(t *testing.T) {
l := lexer.New("null") l := lexer.New("null")
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -477,7 +477,7 @@ func TestIfElseExpression(t *testing.T) {
input := `if (x < y) { x } else { y }` input := `if (x < y) { x } else { y }`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -536,7 +536,7 @@ func TestIfElseIfExpression(t *testing.T) {
input := `if (x < y) { x } else if (x == y) { y }` input := `if (x < y) { x } else if (x == y) { y }`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -615,7 +615,7 @@ func TestFunctionLiteralParsing(t *testing.T) {
input := `fn(x, y) { x + y; }` input := `fn(x, y) { x + y; }`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -670,7 +670,7 @@ func TestFunctionParameterParsing(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -692,7 +692,7 @@ func TestCallExpressionParsing(t *testing.T) {
input := "add(1, 2 * 3, 4 + 5);" input := "add(1, 2 * 3, 4 + 5);"
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -751,7 +751,7 @@ func TestCallExpressionParameterParsing(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -784,7 +784,7 @@ func TestStringLiteralExpression(t *testing.T) {
input := `"hello world";` input := `"hello world";`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -803,7 +803,7 @@ func TestParsingArrayLiterals(t *testing.T) {
input := "[1, 2 * 2, 3 + 3]" input := "[1, 2 * 2, 3 + 3]"
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -826,7 +826,7 @@ func TestParsingSelectorExpressions(t *testing.T) {
input := "myHash.foo" input := "myHash.foo"
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
stmt, ok := program.Statements[0].(*ast.ExpressionStatement) stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
@@ -860,7 +860,7 @@ func TestParsingIndexExpressions(t *testing.T) {
input := "myArray[1 + 1]" input := "myArray[1 + 1]"
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -883,7 +883,7 @@ func TestParsingHashLiteralsStringKeys(t *testing.T) {
input := `{"one": 1, "two": 2, "three": 3}` input := `{"one": 1, "two": 2, "three": 3}`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -919,7 +919,7 @@ func TestParsingEmptyHashLiteral(t *testing.T) {
input := "{}" input := "{}"
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -938,7 +938,7 @@ func TestParsingHashLiteralsWithExpressions(t *testing.T) {
input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}` input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -987,7 +987,7 @@ func TestFunctionDefinitionParsing(t *testing.T) {
input := `add := fn(x, y) { x + y; }` input := `add := fn(x, y) { x + y; }`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -998,7 +998,7 @@ func TestWhileExpression(t *testing.T) {
input := `while (x < y) { x }` input := `while (x < y) { x }`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -1055,7 +1055,7 @@ func TestAssignmentExpressions(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -1197,7 +1197,7 @@ func TestComments(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
@@ -1240,7 +1240,7 @@ func TestParsingImportExpressions(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
l := lexer.New(tt.input) l := lexer.New(tt.input)
p := New(l) p := New("<test>", l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)

View File

@@ -1,257 +0,0 @@
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")
}
}

View File

@@ -1,18 +0,0 @@
package internal
import (
"fmt"
)
var (
// Version release version
Version = "0.0.1"
// GitCommit will be overwritten automatically by the build system
GitCommit = "HEAD"
)
// FullVersion returns the full version and commit hash
func FullVersion() string {
return fmt.Sprintf("%s@%s", Version, GitCommit)
}

View File

@@ -2,7 +2,6 @@ package vm
import ( import (
"fmt" "fmt"
"io/ioutil"
"log" "log"
"monkey/internal/builtins" "monkey/internal/builtins"
"monkey/internal/code" "monkey/internal/code"
@@ -11,6 +10,8 @@ import (
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/parser" "monkey/internal/parser"
"monkey/internal/utils" "monkey/internal/utils"
"os"
"path/filepath"
"strings" "strings"
"unicode" "unicode"
) )
@@ -30,20 +31,20 @@ func ExecModule(name string, state *VMState) (object.Object, error) {
return nil, fmt.Errorf("ImportError: no module named '%s'", name) return nil, fmt.Errorf("ImportError: no module named '%s'", name)
} }
b, err := ioutil.ReadFile(filename) b, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("IOError: error reading module '%s': %s", name, err) return nil, fmt.Errorf("IOError: error reading module '%s': %s", name, err)
} }
l := lexer.New(string(b)) l := lexer.New(string(b))
p := parser.New(l) p := parser.New(fmt.Sprintf("<module %s>", name), l)
module := p.ParseProgram() module := p.ParseProgram()
if len(p.Errors()) != 0 { if len(p.Errors()) != 0 {
return nil, fmt.Errorf("ParseError: %s", p.Errors()) return nil, fmt.Errorf("ParseError: %s", p.Errors())
} }
c := compiler.NewWithState(state.Symbols, state.Constants) c := compiler.NewWithState(state.Symbols, &state.Constants)
err = c.Compile(module) err = c.Compile(module)
if err != nil { if err != nil {
return nil, fmt.Errorf("CompileError: %s", err) return nil, fmt.Errorf("CompileError: %s", err)
@@ -52,7 +53,7 @@ func ExecModule(name string, state *VMState) (object.Object, error) {
code := c.Bytecode() code := c.Bytecode()
state.Constants = code.Constants state.Constants = code.Constants
machine := NewWithState(code, state) machine := NewWithState(fmt.Sprintf("<module %s>", name), code, state)
err = machine.Run() err = machine.Run()
if err != nil { if err != nil {
return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err) return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err)
@@ -102,6 +103,9 @@ type VM struct {
state *VMState state *VMState
dir string
file string
stack []object.Object stack []object.Object
sp int // Always points to the next value. Top of stack is stack[sp-1] sp int // Always points to the next value. Top of stack is stack[sp-1]
@@ -111,7 +115,7 @@ type VM struct {
} }
// New constructs a new monkey-lang bytecode virtual machine // New constructs a new monkey-lang bytecode virtual machine
func New(bytecode *compiler.Bytecode) *VM { func New(fn string, bytecode *compiler.Bytecode) *VM {
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := &object.Closure{Fn: mainFn} mainClosure := &object.Closure{Fn: mainFn}
mainFrame := NewFrame(mainClosure, 0) mainFrame := NewFrame(mainClosure, 0)
@@ -122,7 +126,7 @@ func New(bytecode *compiler.Bytecode) *VM {
state := NewVMState() state := NewVMState()
state.Constants = bytecode.Constants state.Constants = bytecode.Constants
return &VM{ vm := &VM{
state: state, state: state,
stack: make([]object.Object, StackSize), stack: make([]object.Object, StackSize),
@@ -132,9 +136,13 @@ func New(bytecode *compiler.Bytecode) *VM {
frame: mainFrame, frame: mainFrame,
fp: 1, fp: 1,
} }
vm.dir, vm.file = filepath.Split(fn)
return vm
} }
func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM { func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM {
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := &object.Closure{Fn: mainFn} mainClosure := &object.Closure{Fn: mainFn}
mainFrame := NewFrame(mainClosure, 0) mainFrame := NewFrame(mainClosure, 0)
@@ -142,7 +150,7 @@ func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
frames := make([]*Frame, MaxFrames) frames := make([]*Frame, MaxFrames)
frames[0] = mainFrame frames[0] = mainFrame
return &VM{ vm := &VM{
state: state, state: state,
frames: frames, frames: frames,
@@ -152,6 +160,10 @@ func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
stack: make([]object.Object, StackSize), stack: make([]object.Object, StackSize),
sp: 0, sp: 0,
} }
vm.dir, vm.file = filepath.Split(fn)
return vm
} }
func (vm *VM) pushFrame(f *Frame) { func (vm *VM) pushFrame(f *Frame) {
@@ -429,7 +441,7 @@ func (vm *VM) Run() error {
case code.OpGetBuiltin: case code.OpGetBuiltin:
builtinIndex := code.ReadUint8(ins[ip+1:]) builtinIndex := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1 vm.frame.ip++
builtin := builtins.BuiltinsIndex[builtinIndex] builtin := builtins.BuiltinsIndex[builtinIndex]

View File

@@ -49,7 +49,7 @@ func runVmTests(t *testing.T, tests []vmTestCase) {
// fmt.Printf("\n") // fmt.Printf("\n")
//} //}
vm := New(comp.Bytecode()) vm := New("<test>", comp.Bytecode())
err = vm.Run() err = vm.Run()
if err != nil { if err != nil {
t.Fatalf("vm error: %s", err) t.Fatalf("vm error: %s", err)
@@ -148,7 +148,7 @@ func testExpectedObject(t *testing.T, expected interface{}, actual object.Object
func parse(input string) *ast.Program { func parse(input string) *ast.Program {
l := lexer.New(input) l := lexer.New(input)
p := parser.New(l) p := parser.New("<test>", l)
return p.ParseProgram() return p.ParseProgram()
} }
@@ -694,7 +694,7 @@ func TestCallingFunctionsWithWrongArguments(t *testing.T) {
t.Fatalf("compiler error: %s", err) t.Fatalf("compiler error: %s", err)
} }
vm := New(comp.Bytecode()) vm := New("<test>", comp.Bytecode())
err = vm.Run() err = vm.Run()
if err == nil { if err == nil {
t.Fatalf("expected VM error but resulted in none.") t.Fatalf("expected VM error but resulted in none.")
@@ -1040,7 +1040,7 @@ func TestIntegration(t *testing.T) {
t.Fatalf("compiler error: %s", err) t.Fatalf("compiler error: %s", err)
} }
vm := New(c.Bytecode()) vm := New("<test>", c.Bytecode())
err = vm.Run() err = vm.Run()
if err != nil { if err != nil {
@@ -1089,7 +1089,7 @@ func TestExamples(t *testing.T) {
t.Fatalf("compiler error: %s", err) t.Fatalf("compiler error: %s", err)
} }
vm := New(c.Bytecode()) vm := New("<test>", c.Bytecode())
err = vm.Run() err = vm.Run()
if err != nil { if err != nil {
@@ -1166,7 +1166,7 @@ func BenchmarkFibonacci(b *testing.B) {
b.Fatalf("compiler error: %s", err) b.Fatalf("compiler error: %s", err)
} }
vm := New(c.Bytecode()) vm := New("<test>", c.Bytecode())
err = vm.Run() err = vm.Run()
if err != nil { if err != nil {
@@ -1185,15 +1185,15 @@ func BenchmarkFibonacci(b *testing.B) {
func TestImportExpressions(t *testing.T) { func TestImportExpressions(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: `mod := import("../../testdata/mod"); mod.A`, input: `mod := import("../testdata/mod"); mod.A`,
expected: 5, expected: 5,
}, },
{ {
input: `mod := import("../../testdata/mod"); mod.Sum(2, 3)`, input: `mod := import("../testdata/mod"); mod.Sum(2, 3)`,
expected: 5, expected: 5,
}, },
{ {
input: `mod := import("../../testdata/mod"); mod.a`, input: `mod := import("../testdata/mod"); mod.a`,
expected: nil, expected: nil,
}, },
} }
@@ -1202,11 +1202,11 @@ func TestImportExpressions(t *testing.T) {
} }
func TestImportSearchPaths(t *testing.T) { func TestImportSearchPaths(t *testing.T) {
utils.AddPath("../../testdata") utils.AddPath("../testdata")
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: `mod := import("../../testdata/mod"); mod.A`, input: `mod := import("../testdata/mod"); mod.A`,
expected: 5, expected: 5,
}, },
} }

140
monkey.go Normal file
View File

@@ -0,0 +1,140 @@
package monkey
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"monkey/internal/ast"
"monkey/internal/compiler"
"monkey/internal/parser"
"monkey/internal/vm"
)
const MonkeyVersion = "v0.0.1"
var ErrParseError = errors.New("error: parse error")
func mustReadFile(fname string) []byte {
b, err := os.ReadFile(fname)
if err != nil {
panic(err)
}
return b
}
func writeFile(fname string, cont []byte) {
if err := os.WriteFile(fname, cont, 0644); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func decode(path string) (*compiler.Bytecode, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return compiler.Decode(b), nil
}
func compile(path string) (bc *compiler.Bytecode, err error) {
input := string(mustReadFile(path))
res, errs := parser.Parse(path, input)
if len(errs) > 0 {
var buf strings.Builder
for _, e := range errs {
buf.WriteString(e)
buf.WriteByte('\n')
}
return nil, errors.New(buf.String())
}
c := compiler.New()
c.SetFileInfo(path, input)
if err = c.Compile(res); err != nil {
return
}
return c.Bytecode(), nil
}
func ExecFileVM(f string) (err error) {
var bytecode *compiler.Bytecode
if filepath.Ext(f) == ".monkeyc" {
bytecode, err = decode(f)
} else {
bytecode, err = compile(f)
}
if err != nil {
fmt.Println(err)
return
}
mvm := vm.New(f, bytecode)
if err = mvm.Run(); err != nil {
fmt.Println(err)
return
}
return
}
func CompileFiles(files []string) error {
for _, f := range files {
b := mustReadFile(f)
res, errs := parser.Parse(f, string(b))
if len(errs) != 0 {
for _, e := range errs {
fmt.Println(e)
}
return ErrParseError
}
c := compiler.New()
if err := c.Compile(res); err != nil {
fmt.Println(err)
continue
}
cnt, err := c.Bytecode().Encode()
if err != nil {
fmt.Println(err)
continue
}
ext := filepath.Ext(f)
writeFile(f[:len(f)-len(ext)]+".monkeyc", cnt)
}
return nil
}
func PrintVersionInfo(w io.Writer) {
fmt.Fprintf(w, "Monkey %s on %s\n", MonkeyVersion, strings.Title(runtime.GOOS))
}
func Parse(src string) (ast.Node, error) {
tree, errs := parser.Parse("<input>", src)
if len(errs) > 0 {
var buf strings.Builder
buf.WriteString("parser error:\n")
for _, e := range errs {
buf.WriteString(e)
buf.WriteByte('\n')
}
return nil, errors.New(buf.String())
}
return tree, nil
}

191
repl.go Normal file
View File

@@ -0,0 +1,191 @@
package monkey
import (
"bufio"
"fmt"
"io"
"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
func VmREPL() error {
var state = vm.NewVMState()
initState, err := term.MakeRaw(0)
if err != nil {
fmt.Println(err)
return fmt.Errorf("error opening terminal: %w", err)
}
defer term.Restore(0, initState)
t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete
object.Stdout = t
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
}
c := compiler.NewWithState(state.Symbols, &state.Constants)
if err := c.Compile(res); err != nil {
fmt.Fprintln(t, err)
continue
}
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
if err := mvm.Run(); err != nil {
fmt.Fprintf(t, "runtime error: %v\n", err)
continue
}
if val := mvm.LastPoppedStackElem(); val.Type() != object.NULL_OBJ {
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
}
func SimpleVmREPL() {
var (
state = vm.NewVMState()
reader = bufio.NewReader(os.Stdin)
)
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
}
c := compiler.NewWithState(state.Symbols, &state.Constants)
c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil {
fmt.Println(err)
continue
}
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
if err := mvm.Run(); err != nil {
fmt.Printf("runtime error: %v\n", err)
continue
}
if val := mvm.LastPoppedStackElem(); val.Type() != object.NULL_OBJ {
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
}