refactor how repl works
This commit is contained in:
@@ -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
1
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|||||||
147
internal/compiler/bytecode.go
Normal file
147
internal/compiler/bytecode.go
Normal 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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -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
140
monkey.go
Normal 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
191
repl.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user