lots o fixes
Some checks failed
Build / build (push) Successful in 9m50s
Publish Image / publish (push) Failing after 49s
Test / build (push) Successful in 10m55s

This commit is contained in:
2024-04-01 18:18:45 -04:00
parent fe33fda0ab
commit 862119e90e
44 changed files with 326 additions and 170 deletions

1
.gitignore vendored
View File

@@ -79,6 +79,7 @@ fabric.properties
*~ *~
*-e *-e
*.mc
*.so *.so
*.bak *.bak
*.out *.out

View File

@@ -73,7 +73,7 @@ compare: ## Run benchmarks comparing Monkey with other languages
'go run examples/fib.go' \ 'go run examples/fib.go' \
'tengo examples/fib.tengo' \ 'tengo examples/fib.tengo' \
'python3 examples/fib.py' \ 'python3 examples/fib.py' \
'./monkey examples/fib.monkey' './monkey examples/fib.m'
bench: # Run test benchmarks bench: # Run test benchmarks
@go test -v -benchmem -bench=. ./... @go test -v -benchmem -bench=. ./...

View File

@@ -2,8 +2,10 @@
currentBranch="$(git branch --show-current)" currentBranch="$(git branch --show-current)"
N="${1:-35}"
hyperfine \ hyperfine \
-w 5 \ -w 5 \
-p "make build" \ -p "make build" \
-n "$currentBranch" \ -n "$currentBranch" \
'./monkey examples/fib.monkey' "./monkey examples/fib.m $N"

View File

@@ -31,12 +31,12 @@ func main() {
monkey.PrintVersionInfo(os.Stdout) monkey.PrintVersionInfo(os.Stdout)
case flag.NArg() > 0: case flag.NArg() > 0:
monkey.ExecFileVM(flag.Arg(0), flag.Args()[1:], debug, trace) monkey.ExecFile(flag.Arg(0), flag.Args()[1:], debug, trace)
case simple: case simple:
monkey.SimpleVmREPL(flag.Args(), debug, trace) monkey.SimpleREPL(flag.Args(), debug, trace)
default: default:
monkey.VmREPL(flag.Args(), debug, trace) monkey.REPL(flag.Args(), debug, trace)
} }
} }

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
if [ $# -ne 1 ]; then if [ $# -lt 1 ]; then
echo "Usage: $(basename "$0") <branch>" echo "Usage: $(basename "$0") <branch>"
exit 1 exit 1
fi fi
@@ -8,6 +8,8 @@ fi
compareWith="$1" compareWith="$1"
currentBranch="$(git branch --show-current)" currentBranch="$(git branch --show-current)"
N="${2:-35}"
if [ -n "$(git status --porcelain)" ]; then if [ -n "$(git status --porcelain)" ]; then
echo "$currentBranch is not clean, please stash or commit your changes!" echo "$currentBranch is not clean, please stash or commit your changes!"
exit 1 exit 1
@@ -20,5 +22,5 @@ hyperfine \
-p "git checkout $compareWith; make build" \ -p "git checkout $compareWith; make build" \
-n "$currentBranch" \ -n "$currentBranch" \
-n "$compareWith" \ -n "$compareWith" \
'./monkey examples/fib.monkey' \ "./monkey examples/fib.m $N" \
'./monkey examples/fib.monkey' "./monkey examples/fib.m $N"

View File

@@ -54,7 +54,7 @@ read := fn() {
} }
write := fn(x) { write := fn(x) {
print(chr(x)) println(chr(x))
} }
VM := fn(program) { VM := fn(program) {
@@ -99,12 +99,12 @@ VM := fn(program) {
ip = ip + 1 ip = ip + 1
} }
print("memory:") println("memory:")
print(memory) println(memory)
print("ip:") println("ip:")
print(ip) println(ip)
print("dp:") println("dp:")
print(dp) println(dp)
} }
// Hello World // Hello World

View File

@@ -10,7 +10,7 @@ book := {
printBookName := fn(book) { printBookName := fn(book) {
title := book["title"]; title := book["title"];
author := book["author"]; author := book["author"];
print(author + " - " + title); println(author + " - " + title);
}; };
printBookName(book); printBookName(book);

View File

@@ -4,5 +4,5 @@ fd := socket("tcp4")
bind(fd, "127.0.0.1:32535") bind(fd, "127.0.0.1:32535")
connect(fd, "127.0.0.1:8000") connect(fd, "127.0.0.1:8000")
write(fd, "Hello World") write(fd, "Hello World")
print(read(fd)) println(read(fd))
close(fd) close(fd)

15
examples/embed.go Normal file
View File

@@ -0,0 +1,15 @@
//go:build ignore
package main
import (
"log"
)
const program = `println("Hello World!")`
func main() {
if err := monkey.ExecString(program, false, false); err != nil {
log.Fatal("error running program")
}
}

View File

@@ -5,4 +5,4 @@ fact := fn(n, a) {
return fact(n - 1, a * n) return fact(n - 1, a * n)
} }
print(fact(5, 1)) println(fact(5, 1))

View File

@@ -11,4 +11,4 @@ if (len(args()) > 0) {
N = int(args()[0]) N = int(args()[0])
} }
print(fib(N)) println(fib(N))

View File

@@ -21,4 +21,4 @@ if (len(args()) > 0) {
N = int(args()[0]) N = int(args()[0])
} }
print(fib(N)) println(fib(N))

View File

@@ -14,4 +14,4 @@ if (len(args()) > 0) {
N = int(args()[0]) N = int(args()[0])
} }
print(fib(N, 0, 1)) println(fib(N, 0, 1))

View File

@@ -14,6 +14,6 @@ test := fn(n) {
n := 1 n := 1
while (n <= 100) { while (n <= 100) {
print(test(n)) println(test(n))
n = n + 1 n = n + 1
} }

1
examples/hello.m Normal file
View File

@@ -0,0 +1 @@
println("Hello World!")

View File

@@ -1 +0,0 @@
print("Hello World!")

View File

@@ -1,3 +1,3 @@
name := input("What is your name? ") name := input("What is your name? ")
print("Hello " + name) println("Hello " + name)

View File

@@ -35,7 +35,7 @@ if (len(args()) == 1) {
n := 1 n := 1
while (n < N) { while (n < N) {
if (prime(n)) { if (prime(n)) {
print(n) println(n)
} }
n = n + 1 n = n + 1
} }

View File

@@ -11,6 +11,7 @@ var Builtins = map[string]object.Builtin{
"len": {Name: "len", Fn: Len}, "len": {Name: "len", Fn: Len},
"input": {Name: "input", Fn: Input}, "input": {Name: "input", Fn: Input},
"print": {Name: "print", Fn: Print}, "print": {Name: "print", Fn: Print},
"println": {Name: "println", Fn: Println},
"first": {Name: "first", Fn: First}, "first": {Name: "first", Fn: First},
"last": {Name: "last", Fn: Last}, "last": {Name: "last", Fn: Last},
"rest": {Name: "rest", Fn: Rest}, "rest": {Name: "rest", Fn: Rest},

View File

@@ -16,7 +16,21 @@ func Print(args ...object.Object) object.Object {
return newError(err.Error()) return newError(err.Error())
} }
fmt.Println(args[0].String()) fmt.Fprint(object.Stdout, args[0].String())
return nil
}
// Println ...
func Println(args ...object.Object) object.Object {
if err := typing.Check(
"println", args,
typing.MinimumArgs(1),
); err != nil {
return newError(err.Error())
}
fmt.Fprintln(object.Stdout, args[0].String())
return nil return nil
} }

View File

@@ -5,23 +5,22 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math"
"monkey/internal/code" "monkey/internal/code"
"monkey/internal/object" "monkey/internal/object"
"strings" "strings"
) )
func Indent(text, ident string) string { func indent(text, indent string) string {
if text[len(text)-1:] == "\n" { if text[len(text)-1:] == "\n" {
result := "" result := ""
for _, j := range strings.Split(text[:len(text)-1], "\n") { for _, j := range strings.Split(text[:len(text)-1], "\n") {
result += ident + j + "\n" result += indent + j + "\n"
} }
return result return result
} }
result := "" result := ""
for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") { for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
result += ident + j + "\n" result += indent + j + "\n"
} }
return result[:len(result)-1] return result[:len(result)-1]
} }
@@ -38,7 +37,7 @@ func (b *Bytecode) String() string {
for i, c := range b.Constants { for i, c := range b.Constants {
s.WriteString(fmt.Sprintf("%02d %s\n", i, c)) s.WriteString(fmt.Sprintf("%02d %s\n", i, c))
if cf, ok := c.(*object.CompiledFunction); ok { if cf, ok := c.(*object.CompiledFunction); ok {
s.WriteString(fmt.Sprintf(" Instructions:\n%s\n", Indent(cf.Instructions.String(), " "))) s.WriteString(fmt.Sprintf(" Instructions:\n%s\n", indent(cf.Instructions.String(), " ")))
} }
} }
@@ -51,22 +50,40 @@ type encoder struct {
bytes.Buffer bytes.Buffer
} }
func (e *encoder) Write(b []byte) (err error) { func (e *encoder) WriteBytes(b []byte) error {
_, err = e.Buffer.Write(b) if err := e.WriteValue(len(b)); err != nil {
return return err
}
if _, err := e.Buffer.Write(b); err != nil {
return err
}
return nil
} }
func (e *encoder) WriteString(s string) (err error) { func (e *encoder) WriteString(s string) error {
_, err = e.Buffer.WriteString(s) if err := e.WriteValue(len(s)); err != nil {
return return err
}
if _, err := e.Buffer.WriteString(s); err != nil {
return err
}
return nil
} }
func (e *encoder) WriteValue(data any) error { func (e *encoder) WriteValue(data any) error {
return binary.Write(&e.Buffer, binary.BigEndian, data) switch val := data.(type) {
case int:
return binary.Write(&e.Buffer, binary.BigEndian, int64(val))
case object.Type:
return e.WriteValue(int(val))
default:
return binary.Write(&e.Buffer, binary.BigEndian, data)
}
} }
func (e *encoder) WriteObjects(objs ...object.Object) (err error) { func (e *encoder) WriteObjects(objects ...object.Object) (err error) {
for _, obj := range objs { err = errors.Join(err, e.WriteValue(len(objects)))
for _, obj := range objects {
err = errors.Join(err, e.WriteValue(obj.Type())) err = errors.Join(err, e.WriteValue(obj.Type()))
switch o := obj.(type) { switch o := obj.(type) {
@@ -77,13 +94,11 @@ func (e *encoder) WriteObjects(objs ...object.Object) (err error) {
case object.Integer: case object.Integer:
err = errors.Join(err, e.WriteValue(o.Value)) err = errors.Join(err, e.WriteValue(o.Value))
case object.String: case object.String:
err = errors.Join(err, e.WriteValue(len(o.Value))) err = errors.Join(err, e.WriteString(o.Value))
err = errors.Join(err, e.WriteValue(o.Value))
case *object.CompiledFunction: case *object.CompiledFunction:
err = errors.Join(err, e.WriteValue(o.NumParameters)) err = errors.Join(err, e.WriteValue(o.NumParameters))
err = errors.Join(err, e.WriteValue(o.NumLocals)) err = errors.Join(err, e.WriteValue(o.NumLocals))
err = errors.Join(err, e.WriteValue(len(o.Instructions))) err = errors.Join(err, e.WriteBytes(o.Instructions))
err = errors.Join(err, e.Write(o.Instructions))
} }
} }
return return
@@ -92,9 +107,7 @@ func (e *encoder) WriteObjects(objs ...object.Object) (err error) {
func (b Bytecode) Encode() (data []byte, err error) { func (b Bytecode) Encode() (data []byte, err error) {
var e encoder var e encoder
err = errors.Join(err, e.WriteValue(len(b.Instructions))) err = errors.Join(err, e.WriteBytes(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...)) err = errors.Join(err, e.WriteObjects(b.Constants...))
return e.Bytes(), err return e.Bytes(), err
} }
@@ -110,10 +123,8 @@ func (d *decoder) Byte() (b byte) {
return return
} }
func (d *decoder) Int() (i int) { func (d *decoder) Int() int {
i = int(binary.BigEndian.Uint32(d.b[d.pos:])) return int(d.Int64())
d.pos += 4
return
} }
func (d *decoder) Uint64() (i uint64) { func (d *decoder) Uint64() (i uint64) {
@@ -122,14 +133,10 @@ func (d *decoder) Uint64() (i uint64) {
return return
} }
func (d *decoder) Int64() (i int64) { func (d *decoder) Int64() int64 {
return int64(d.Uint64()) return int64(d.Uint64())
} }
func (d *decoder) Float64() (f float64) {
return math.Float64frombits(d.Uint64())
}
func (d *decoder) Bytes(len int) (b []byte) { func (d *decoder) Bytes(len int) (b []byte) {
b = d.b[d.pos : d.pos+len] b = d.b[d.pos : d.pos+len]
d.pos += len d.pos += len
@@ -161,7 +168,7 @@ func (d *decoder) Objects(len int) (o []object.Object) {
Instructions: d.Bytes(d.Int()), Instructions: d.Bytes(d.Int()),
}) })
default: default:
panic(fmt.Sprintf("decoder: unsupported decoding for type %d", t)) panic(fmt.Sprintf("decoder: unsupported decoding for type %d (%s)", t, t))
} }
} }
return return

View File

@@ -0,0 +1,55 @@
package compiler
import (
"monkey/internal/lexer"
"monkey/internal/parser"
"regexp"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var hexRe = regexp.MustCompile(`0x[0-9a-f]+`)
func compile(input string) (*Bytecode, error) {
l := lexer.New(input)
p := parser.New("<text", l)
c := New()
if err := c.Compile(p.ParseProgram()); err != nil {
return nil, err
}
return c.Bytecode(), nil
}
func TestEncodeDecode(t *testing.T) {
input := `
fib := fn(x) {
if (x == 0) {
return 0
}
if (x == 1) {
return 1
}
return fib(x-1) + fib(x-2)
}
fib(35)
`
assert := assert.New(t)
require := require.New(t)
bc, err := compile(input)
require.NoError(err)
encoded, err := bc.Encode()
require.NoError(err)
decoded := Decode(encoded)
expected := hexRe.ReplaceAllString(bc.String(), "0x1234")
actual := hexRe.ReplaceAllString(decoded.String(), "0x1234")
assert.EqualValues(expected, actual)
}

View File

@@ -948,7 +948,7 @@ func TestBuiltins(t *testing.T) {
code.Make(code.OpArray, 0), code.Make(code.OpArray, 0),
code.Make(code.OpCall, 1), code.Make(code.OpCall, 1),
code.Make(code.OpPop), code.Make(code.OpPop),
code.Make(code.OpGetBuiltin, 33), code.Make(code.OpGetBuiltin, 34),
code.Make(code.OpArray, 0), code.Make(code.OpArray, 0),
code.Make(code.OpConstant, 0), code.Make(code.OpConstant, 0),
code.Make(code.OpCall, 2), code.Make(code.OpCall, 2),

View File

@@ -889,7 +889,7 @@ func TestImportSearchPaths(t *testing.T) {
} }
func TestExamples(t *testing.T) { func TestExamples(t *testing.T) {
matches, err := filepath.Glob("../../examples/*.monkey") matches, err := filepath.Glob("../../examples/*.m")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@@ -42,7 +42,7 @@ func Exists(path string) bool {
} }
func FindModule(name string) string { func FindModule(name string) string {
basename := fmt.Sprintf("%s.monkey", name) basename := fmt.Sprintf("%s.m", name)
for _, p := range SearchPaths { for _, p := range SearchPaths {
filename := filepath.Join(p, basename) filename := filepath.Join(p, basename)
if Exists(filename) { if Exists(filename) {

View File

@@ -5,57 +5,57 @@ import (
"monkey/internal/object" "monkey/internal/object"
) )
type Frame struct { type frame struct {
cl *object.Closure cl *object.Closure
ip int ip int
basePointer int basePointer int
} }
func NewFrame(cl *object.Closure, basePointer int) Frame { func newFrame(cl *object.Closure, basePointer int) frame {
return Frame{ return frame{
cl: cl, cl: cl,
basePointer: basePointer, basePointer: basePointer,
} }
} }
func (f *Frame) Closure() *object.Closure { func (f *frame) Closure() *object.Closure {
return f.cl return f.cl
} }
func (f *Frame) GetFree(idx uint8) object.Object { func (f *frame) GetFree(idx uint8) object.Object {
return f.cl.Free[idx] return f.cl.Free[idx]
} }
func (f *Frame) SetFree(idx uint8, obj object.Object) { func (f *frame) SetFree(idx uint8, obj object.Object) {
f.cl.Free[idx] = obj f.cl.Free[idx] = obj
} }
func (f *Frame) SetIP(ip int) { func (f *frame) SetIP(ip int) {
f.ip = ip f.ip = ip
} }
func (f Frame) PeekNextOp() code.Opcode { func (f frame) PeekNextOp() code.Opcode {
return code.Opcode(f.cl.Fn.Instructions[f.ip]) return code.Opcode(f.cl.Fn.Instructions[f.ip])
} }
func (f *Frame) ReadNextOp() code.Opcode { func (f *frame) ReadNextOp() code.Opcode {
op := code.Opcode(f.cl.Fn.Instructions[f.ip]) op := code.Opcode(f.cl.Fn.Instructions[f.ip])
f.ip++ f.ip++
return op return op
} }
func (f *Frame) ReadUint8() uint8 { func (f *frame) ReadUint8() uint8 {
n := code.ReadUint8(f.cl.Fn.Instructions[f.ip:]) n := code.ReadUint8(f.cl.Fn.Instructions[f.ip:])
f.ip++ f.ip++
return n return n
} }
func (f *Frame) ReadUint16() uint16 { func (f *frame) ReadUint16() uint16 {
n := code.ReadUint16(f.cl.Fn.Instructions[f.ip:]) n := code.ReadUint16(f.cl.Fn.Instructions[f.ip:])
f.ip += 2 f.ip += 2
return n return n
} }
func (f Frame) Instructions() code.Instructions { func (f frame) Instructions() code.Instructions {
return f.cl.Fn.Instructions return f.cl.Fn.Instructions
} }

View File

@@ -17,9 +17,11 @@ import (
"unicode" "unicode"
) )
const StackSize = 2048 const (
const GlobalsSize = 65536 maxStackSize = 2048
const MaxFrames = 1024 maxFrames = 1024
maxGlobals = 65536
)
func isTruthy(obj object.Object) bool { func isTruthy(obj object.Object) bool {
switch obj := obj.(type) { switch obj := obj.(type) {
@@ -36,7 +38,7 @@ func isTruthy(obj object.Object) bool {
} }
// executeModule compiles the named module and returns a object.Module object // executeModule compiles the named module and returns a object.Module object
func executeModule(name string, state *VMState) (object.Object, error) { func executeModule(name string, state *State) (object.Object, error) {
filename := utils.FindModule(name) filename := utils.FindModule(name)
if filename == "" { if filename == "" {
return nil, fmt.Errorf("ImportError: no module named '%s'", name) return nil, fmt.Errorf("ImportError: no module named '%s'", name)
@@ -73,21 +75,22 @@ func executeModule(name string, state *VMState) (object.Object, error) {
return state.ExportedHash(), nil return state.ExportedHash(), nil
} }
type VMState struct { // State is the state of the virtual machine.
type State struct {
Constants []object.Object Constants []object.Object
Globals []object.Object Globals []object.Object
Symbols *compiler.SymbolTable Symbols *compiler.SymbolTable
} }
func NewVMState() *VMState { func NewState() *State {
symbolTable := compiler.NewSymbolTable() symbolTable := compiler.NewSymbolTable()
for i, builtin := range builtins.BuiltinsIndex { for i, builtin := range builtins.BuiltinsIndex {
symbolTable.DefineBuiltin(i, builtin.Name) symbolTable.DefineBuiltin(i, builtin.Name)
} }
return &VMState{ return &State{
Constants: []object.Object{}, Constants: []object.Object{},
Globals: make([]object.Object, GlobalsSize), Globals: make([]object.Object, maxGlobals),
Symbols: symbolTable, Symbols: symbolTable,
} }
} }
@@ -95,7 +98,7 @@ func NewVMState() *VMState {
// exported binding in the vm state. That is every binding that starts with a // exported binding in the vm state. That is every binding that starts with a
// capital letter. This is used by the module import system to wrap up the // capital letter. This is used by the module import system to wrap up the
// compiled and evaulated module into an object. // compiled and evaulated module into an object.
func (s *VMState) ExportedHash() *object.Hash { func (s *State) ExportedHash() *object.Hash {
pairs := make(map[object.HashKey]object.HashPair) pairs := make(map[object.HashKey]object.HashPair)
for name, symbol := range s.Symbols.Store { for name, symbol := range s.Symbols.Store {
if unicode.IsUpper(rune(name[0])) { if unicode.IsUpper(rune(name[0])) {
@@ -113,7 +116,7 @@ type VM struct {
Debug bool Debug bool
Trace bool Trace bool
state *VMState state *State
dir string dir string
file string file string
@@ -121,16 +124,16 @@ type VM struct {
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]
frames []Frame frames []frame
fp int // Always points to the current frame. Current frame is frames[fp-1] fp int // Always points to the current frame. Current frame is frames[fp-1]
} }
func (vm *VM) currentFrame() *Frame { func (vm *VM) currentFrame() *frame {
return &vm.frames[vm.fp-1] return &vm.frames[vm.fp-1]
} }
func (vm *VM) pushFrame(f Frame) { func (vm *VM) pushFrame(f frame) {
if vm.fp >= MaxFrames { if vm.fp >= maxFrames {
panic("frame overflow") panic("frame overflow")
} }
@@ -138,7 +141,7 @@ func (vm *VM) pushFrame(f Frame) {
vm.fp++ vm.fp++
} }
func (vm *VM) popFrame() Frame { func (vm *VM) popFrame() frame {
if vm.fp == 0 { if vm.fp == 0 {
panic("fame underflow") panic("fame underflow")
} }
@@ -151,18 +154,18 @@ func (vm *VM) popFrame() Frame {
func New(fn string, 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)
frames := make([]Frame, MaxFrames) frames := make([]frame, maxFrames)
frames[0] = mainFrame frames[0] = mainFrame
state := NewVMState() state := NewState()
state.Constants = bytecode.Constants state.Constants = bytecode.Constants
vm := &VM{ vm := &VM{
state: state, state: state,
stack: make([]object.Object, StackSize), stack: make([]object.Object, maxStackSize),
sp: 0, sp: 0,
frames: frames, frames: frames,
@@ -174,12 +177,12 @@ func New(fn string, bytecode *compiler.Bytecode) *VM {
return vm return vm
} }
func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM { func NewWithState(fn string, bytecode *compiler.Bytecode, state *State) *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)
frames := make([]Frame, MaxFrames) frames := make([]frame, maxFrames)
frames[0] = mainFrame frames[0] = mainFrame
vm := &VM{ vm := &VM{
@@ -188,7 +191,7 @@ func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM {
frames: frames, frames: frames,
fp: 1, fp: 1,
stack: make([]object.Object, StackSize), stack: make([]object.Object, maxStackSize),
sp: 0, sp: 0,
} }
@@ -198,7 +201,7 @@ func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM {
} }
func (vm *VM) push(o object.Object) error { func (vm *VM) push(o object.Object) error {
if vm.sp >= StackSize { if vm.sp >= maxStackSize {
return fmt.Errorf("stack overflow") return fmt.Errorf("stack overflow")
} }
@@ -712,7 +715,7 @@ func (vm *VM) callClosure(cl *object.Closure, numArgs int) error {
} }
} }
frame := NewFrame(cl, vm.sp-numArgs) frame := newFrame(cl, vm.sp-numArgs)
vm.pushFrame(frame) vm.pushFrame(frame)
vm.sp = frame.basePointer + cl.Fn.NumLocals vm.sp = frame.basePointer + cl.Fn.NumLocals

View File

@@ -1015,7 +1015,7 @@ func TestTailCalls(t *testing.T) {
} }
func TestIntegration(t *testing.T) { func TestIntegration(t *testing.T) {
matches, err := filepath.Glob("../testdata/*.monkey") matches, err := filepath.Glob("../testdata/*.m")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -1060,7 +1060,7 @@ func TestExamples(t *testing.T) {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
matches, err := filepath.Glob("../examples/*.monkey") matches, err := filepath.Glob("../examples/*.m")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

188
monkey.go
View File

@@ -1,3 +1,4 @@
// Package monkey provides a virtual machine for executing Monkey programs.
package monkey package monkey
import ( import (
@@ -11,42 +12,37 @@ import (
"runtime" "runtime"
"strings" "strings"
"monkey/internal/ast"
"monkey/internal/compiler" "monkey/internal/compiler"
"monkey/internal/parser" "monkey/internal/parser"
"monkey/internal/vm" "monkey/internal/vm"
) )
// MonkeyVersion is the version number for the monkey language virtual machine.
const MonkeyVersion = "v0.0.1" const MonkeyVersion = "v0.0.1"
var ErrParseError = errors.New("error: parse error") func fileExists(filename string) bool {
info, err := os.Stat(filename)
func mustReadFile(fname string) []byte { if os.IsNotExist(err) {
b, err := os.ReadFile(fname) return false
if err != nil {
panic(err)
} }
return b return !info.IsDir()
} }
func writeFile(fname string, cont []byte) { func decodeCompiledFile(fn string) (*compiler.Bytecode, error) {
if err := os.WriteFile(fname, cont, 0644); err != nil { b, err := os.ReadFile(fn)
fmt.Println(err)
os.Exit(1)
}
}
func decode(path string) (*compiler.Bytecode, error) {
b, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return compiler.Decode(b), nil return compiler.Decode(b), nil
} }
func compile(path string, debug bool) (bc *compiler.Bytecode, err error) { func compileFile(fn string, debug bool) (*compiler.Bytecode, error) {
input := string(mustReadFile(path)) input, err := os.ReadFile(fn)
res, errs := parser.Parse(path, input) if err != nil {
return nil, err
}
res, errs := parser.Parse(fn, string(input))
if len(errs) > 0 { if len(errs) > 0 {
var buf strings.Builder var buf strings.Builder
@@ -63,54 +59,129 @@ func compile(path string, debug bool) (bc *compiler.Bytecode, err error) {
c := compiler.New() c := compiler.New()
c.Debug = debug c.Debug = debug
c.SetFileInfo(path, input) c.SetFileInfo("<stdin>", string(input))
if err = c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
return return nil, err
} }
return c.Bytecode(), nil return c.Bytecode(), nil
} }
func ExecFileVM(f string, args []string, debug, trace bool) (err error) { func compileString(input string, debug bool) (bc *compiler.Bytecode, err error) {
var bytecode *compiler.Bytecode res, errs := parser.Parse("<stdin>", input)
if len(errs) > 0 {
var buf strings.Builder
object.Args = args for _, e := range errs {
buf.WriteString(e)
buf.WriteByte('\n')
}
return nil, errors.New(buf.String())
}
if filepath.Ext(f) == ".mc" { if debug {
bytecode, err = decode(f) log.Printf("AST:\n%s\n", res)
}
c := compiler.New()
c.Debug = debug
c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil {
return nil, err
}
return c.Bytecode(), nil
}
// ExecFile executes a Monkey program from a file on disk.
func ExecFile(fn string, args []string, debug, trace bool) error {
var (
bytecode *compiler.Bytecode
err error
)
ext := filepath.Ext(fn)
mc := fn[:len(fn)-len(ext)] + ".mc"
if ext == ".mc" {
bytecode, err = decodeCompiledFile(fn)
} else if ext == ".m" && fileExists(mc) {
bytecode, err = decodeCompiledFile(mc)
} else { } else {
bytecode, err = compile(f, debug) input, err := os.ReadFile(fn)
if err != nil {
return err
}
bytecode, err = compileString(string(input), debug)
} }
if err != nil { if err != nil {
fmt.Println(err) return err
return
} }
if debug { if debug {
log.Printf("Bytecode:\n%s\n", bytecode) log.Printf("Bytecode:\n%s\n", bytecode)
} }
mvm := vm.New(f, bytecode) if ext == ".m" && !fileExists(mc) {
data, err := bytecode.Encode()
if err == nil {
os.WriteFile(mc, data, os.FileMode(0644))
}
}
object.Args = args
mvm := vm.New(fn, bytecode)
mvm.Debug = debug
mvm.Trace = trace
if err := mvm.Run(); err != nil {
return err
}
return nil
}
// ExecString executes a Monkey program from a string.
func ExecString(input string, debug, trace bool) error {
bytecode, err := compileString(input, debug)
if err != nil {
return err
}
if debug {
log.Printf("Bytecode:\n%s\n", bytecode)
}
mvm := vm.New("<stdin>", bytecode)
mvm.Debug = debug mvm.Debug = debug
mvm.Trace = trace mvm.Trace = trace
if err = mvm.Run(); err != nil { if err = mvm.Run(); err != nil {
fmt.Println(err) return err
return
} }
return return nil
} }
func CompileFiles(files []string, debug bool) error { // CompileFiles compiles multiple Monkey files and returns any errors encountered during compilation.
for _, f := range files { func CompileFiles(fns []string, debug bool) error {
b := mustReadFile(f) for _, fn := range fns {
ext := filepath.Ext(fn)
mc := fn[:len(fn)-len(ext)] + ".mc"
input, err := os.ReadFile(fn)
if err != nil {
return err
}
res, errs := parser.Parse(fn, string(input))
if len(errs) > 0 {
var buf strings.Builder
res, errs := parser.Parse(f, string(b))
if len(errs) != 0 {
for _, e := range errs { for _, e := range errs {
fmt.Println(e) buf.WriteString(e)
buf.WriteByte('\n')
} }
return ErrParseError return errors.New(buf.String())
} }
if debug { if debug {
@@ -120,44 +191,27 @@ func CompileFiles(files []string, debug bool) error {
c := compiler.New() c := compiler.New()
c.Debug = debug c.Debug = debug
if err := c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
fmt.Println(err) return err
continue
} }
if debug { if debug {
log.Printf("Bytecode:\n%s\n", c.Bytecode()) log.Printf("Bytecode:\n%s\n", c.Bytecode())
} }
cnt, err := c.Bytecode().Encode() data, err := c.Bytecode().Encode()
if err != nil { if err != nil {
fmt.Println(err) return err
continue
} }
ext := filepath.Ext(f) if err := os.WriteFile(mc, data, os.FileMode(0644)); err != nil {
writeFile(f[:len(f)-len(ext)]+".mc", cnt) return err
}
} }
return nil return nil
} }
// PrintVersionInfo prints information about the current version of the monkey virtual machine to the given writer.
func PrintVersionInfo(w io.Writer) { func PrintVersionInfo(w io.Writer) {
fmt.Fprintf(w, "Monkey %s on %s\n", MonkeyVersion, strings.Title(runtime.GOOS)) fmt.Fprintf(w, "Monkey %s on %s\n", MonkeyVersion, 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
} }

10
repl.go
View File

@@ -20,8 +20,9 @@ import (
// Package repl implements the Read-Eval-Print-Loop or interactive console // Package repl implements the Read-Eval-Print-Loop or interactive console
// by lexing, parsing and evaluating the input in the interpreter // by lexing, parsing and evaluating the input in the interpreter
func VmREPL(args []string, debug, trace bool) error { // REPL provides a read-eval-print loop for the monkey virtual machine.
var state = vm.NewVMState() func REPL(args []string, debug, trace bool) error {
var state = vm.NewState()
object.Args = args object.Args = args
@@ -142,9 +143,10 @@ func acceptUntil(t *term.Terminal, start, end string) (string, error) {
return buf.String(), nil return buf.String(), nil
} }
func SimpleVmREPL(args []string, debug, trace bool) { // SimpleREPL provides a simple read-eval-print loop for the monkey virtual machine.
func SimpleREPL(args []string, debug, trace bool) {
var ( var (
state = vm.NewVMState() state = vm.NewState()
reader = bufio.NewReader(os.Stdin) reader = bufio.NewReader(os.Stdin)
) )