-
Monkey
⌘/Ctrl + ENTER to Run. ⌘/Ctrl + SPACE to Format.
diff --git a/internal/vm/frame.go b/internal/vm/frame.go
index 416a30b..3ce2d2f 100644
--- a/internal/vm/frame.go
+++ b/internal/vm/frame.go
@@ -6,9 +6,9 @@ import (
)
type frame struct {
- cl *object.Closure
- ip int
+ ip uint16
basePointer int
+ cl *object.Closure
}
func newFrame(cl *object.Closure, basePointer int) frame {
@@ -30,7 +30,7 @@ func (f *frame) SetFree(idx uint8, obj object.Object) {
f.cl.Free[idx] = obj
}
-func (f *frame) SetIP(ip int) {
+func (f *frame) SetIP(ip uint16) {
f.ip = ip
}
diff --git a/internal/vm/vm.go b/internal/vm/vm.go
index 5396c2e..438f18a 100644
--- a/internal/vm/vm.go
+++ b/internal/vm/vm.go
@@ -2,7 +2,6 @@ package vm
import (
"fmt"
- "log"
"monkey/internal/builtins"
"monkey/internal/code"
"monkey/internal/compiler"
@@ -13,8 +12,6 @@ import (
"monkey/internal/utils"
"os"
"path/filepath"
- "sort"
- "time"
"unicode"
)
@@ -151,20 +148,25 @@ func (vm *VM) popFrame() frame {
return vm.frames[vm.fp]
}
+// Option defines a function option for the virtual machine.
type Option func(*VM)
+// WithContext defines an option to set the context for the virtual machine.
func WithContext(ctx context.Context) Option {
return func(vm *VM) { vm.ctx = ctx }
}
+// WithDebug enables debug mode in the VM.
func WithDebug(debug bool) Option {
return func(vm *VM) { vm.debug = debug }
}
+// WithState sets the state of the VM.
func WithState(state *State) Option {
return func(vm *VM) { vm.state = state }
}
+// WithTrace sets the trace flag for the VM.
func WithTrace(trace bool) Option {
return func(vm *VM) { vm.trace = trace }
}
@@ -224,6 +226,29 @@ func (vm *VM) pop() object.Object {
return o
}
+func (vm *VM) pop2() (object.Object, object.Object) {
+ if vm.sp == 1 {
+ panic("stack underflow")
+ }
+
+ o1 := vm.stack[vm.sp-1]
+ o2 := vm.stack[vm.sp-2]
+ vm.sp -= 2
+ return o1, o2
+}
+
+func (vm *VM) pop3() (object.Object, object.Object, object.Object) {
+ if vm.sp == 2 {
+ panic("stack underflow")
+ }
+
+ o1 := vm.stack[vm.sp-1]
+ o2 := vm.stack[vm.sp-2]
+ o3 := vm.stack[vm.sp-3]
+ vm.sp -= 3
+ return o1, o2, o3
+}
+
func (vm *VM) executeSetGlobal() error {
globalIndex := vm.currentFrame().ReadUint16()
@@ -301,8 +326,7 @@ func (vm *VM) executeMakeArray() error {
}
func (vm *VM) executeAdd() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -335,8 +359,7 @@ func (vm *VM) executeAdd() error {
}
func (vm *VM) executeSub() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -351,8 +374,7 @@ func (vm *VM) executeSub() error {
}
func (vm *VM) executeMul() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case *object.Array:
@@ -379,8 +401,7 @@ func (vm *VM) executeMul() error {
}
func (vm *VM) executeDiv() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -396,8 +417,7 @@ func (vm *VM) executeDiv() error {
}
func (vm *VM) executeMod() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -412,8 +432,7 @@ func (vm *VM) executeMod() error {
}
func (vm *VM) executeOr() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Boolean:
@@ -428,8 +447,7 @@ func (vm *VM) executeOr() error {
}
func (vm *VM) executeAnd() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Boolean:
@@ -444,8 +462,7 @@ func (vm *VM) executeAnd() error {
}
func (vm *VM) executeBitwiseOr() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -460,8 +477,7 @@ func (vm *VM) executeBitwiseOr() error {
}
func (vm *VM) executeBitwiseXor() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -476,8 +492,7 @@ func (vm *VM) executeBitwiseXor() error {
}
func (vm *VM) executeBitwiseAnd() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -503,8 +518,7 @@ func (vm *VM) executeBitwiseNot() error {
}
func (vm *VM) executeLeftShift() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -519,8 +533,7 @@ func (vm *VM) executeLeftShift() error {
}
func (vm *VM) executeRightShift() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
switch obj := left.(type) {
case object.Integer:
@@ -535,63 +548,39 @@ func (vm *VM) executeRightShift() error {
}
func (vm *VM) executeEqual() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
- if obj, ok := left.(object.Comparable); ok {
- val := obj.Compare(right)
- if val == 0 {
- return vm.push(object.TRUE)
- }
- return vm.push(object.FALSE)
+ if left.Compare(right) == 0 {
+ return vm.push(object.TRUE)
}
-
- return object.NewBinaryOpError(left, right, "==")
+ return vm.push(object.FALSE)
}
func (vm *VM) executeNotEqual() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
- if obj, ok := left.(object.Comparable); ok {
- val := obj.Compare(right)
- if val != 0 {
- return vm.push(object.TRUE)
- }
- return vm.push(object.FALSE)
+ if left.Compare(right) != 0 {
+ return vm.push(object.TRUE)
}
-
- return object.NewBinaryOpError(left, right, "!=")
+ return vm.push(object.FALSE)
}
func (vm *VM) executeGreaterThan() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
- if obj, ok := left.(object.Comparable); ok {
- val := obj.Compare(right)
- if val == 1 {
- return vm.push(object.TRUE)
- }
- return vm.push(object.FALSE)
+ if left.Compare(right) == 1 {
+ return vm.push(object.TRUE)
}
-
- return object.NewBinaryOpError(left, right, ">")
+ return vm.push(object.FALSE)
}
func (vm *VM) executeGreaterThanOrEqual() error {
- right := vm.pop()
- left := vm.pop()
+ right, left := vm.pop2()
- if obj, ok := left.(object.Comparable); ok {
- val := obj.Compare(right)
- if val >= 0 {
- return vm.push(object.TRUE)
- }
- return vm.push(object.FALSE)
+ if left.Compare(right) == 1 {
+ return vm.push(object.TRUE)
}
-
- return object.NewBinaryOpError(left, right, ">")
+ return vm.push(object.FALSE)
}
func (vm *VM) executeNot() error {
@@ -621,9 +610,7 @@ func (vm *VM) executeMinus() error {
}
func (vm *VM) executeSetItem() error {
- right := vm.pop()
- index := vm.pop()
- left := vm.pop()
+ right, index, left := vm.pop3()
switch obj := left.(type) {
case *object.Array:
@@ -647,8 +634,7 @@ func (vm *VM) executeSetItem() error {
}
func (vm *VM) executeGetItem() error {
- index := vm.pop()
- left := vm.pop()
+ index, left := vm.pop2()
switch obj := left.(type) {
case object.String:
@@ -749,7 +735,7 @@ func (vm *VM) pushClosure(constIndex, numFree int) error {
for i := 0; i < numFree; i++ {
free[i] = vm.stack[vm.sp-numFree+i]
}
- vm.sp = vm.sp - numFree
+ vm.sp -= numFree
closure := object.Closure{Fn: function, Free: free}
return vm.push(&closure)
@@ -778,49 +764,9 @@ func (vm *VM) LastPoppedStackElem() object.Object {
}
func (vm *VM) Run() (err error) {
- var (
- op code.Opcode
- opcodeFreqs = make(map[code.Opcode]int)
- )
-
- if vm.debug {
- start := time.Now()
- defer func() {
- end := time.Now().Sub(start)
- total := 0
- opcodes := make([]code.Opcode, 0, len(opcodeFreqs))
-
- for opcode, count := range opcodeFreqs {
- opcodes = append(opcodes, opcode)
- total += count
- }
-
- sort.SliceStable(opcodes, func(i, j int) bool {
- return opcodeFreqs[opcodes[i]] > opcodeFreqs[opcodes[j]]
- })
-
- log.Printf("%d instructions executed in %s %d/µs", total, end, total/int(end.Microseconds()))
- log.Print("Top Instructions:")
- for _, opcode := range opcodes[:min(len(opcodes), 10)] {
- log.Printf("%10d %s", opcodeFreqs[opcode], opcode)
- }
- }()
- }
for err == nil {
- op = vm.currentFrame().ReadNextOp()
-
- if vm.debug {
- opcodeFreqs[op]++
- log.Printf(
- "%-25s %-20s\n",
- fmt.Sprintf("%04d %s", vm.currentFrame().ip, op),
- fmt.Sprintf(
- "[ip=%02d fp=%02d, sp=%02d]",
- vm.currentFrame().ip, vm.fp-1, vm.sp,
- ),
- )
- }
+ op := vm.currentFrame().ReadNextOp()
switch op {
case code.OpConstant:
@@ -837,11 +783,11 @@ func (vm *VM) Run() (err error) {
err = vm.push(object.FALSE)
case code.OpJump:
- pos := int(vm.currentFrame().ReadUint16())
+ pos := vm.currentFrame().ReadUint16()
vm.currentFrame().SetIP(pos)
case code.OpJumpNotTruthy:
- pos := int(vm.currentFrame().ReadUint16())
+ pos := vm.currentFrame().ReadUint16()
if !isTruthy(vm.pop()) {
vm.currentFrame().SetIP(pos)
}
@@ -979,14 +925,7 @@ func (vm *VM) Run() (err error) {
default:
err = fmt.Errorf("unhandled opcode: %s", op)
}
-
- if vm.trace {
- log.Printf(
- "%-25s [ip=%02d fp=%02d, sp=%02d]",
- "", vm.currentFrame().ip, vm.fp-1, vm.sp,
- )
- }
}
- return err
+ return
}
diff --git a/internal/vm/vm_test.go b/internal/vm/vm_test.go
index c54ff28..0fb5a2f 100644
--- a/internal/vm/vm_test.go
+++ b/internal/vm/vm_test.go
@@ -713,12 +713,10 @@ func TestBuiltinFunctions(t *testing.T) {
{`len("hello world")`, 11},
{
`len(1)`,
- &object.Error{
- Message: "TypeError: object of type 'int' has no len()",
- },
+ fmt.Errorf("TypeError: object of type 'int' has no len()"),
},
{`len("one", "two")`,
- &object.Error{
+ object.Error{
Message: "TypeError: len() takes exactly 1 argument (2 given)",
},
},
@@ -728,14 +726,14 @@ func TestBuiltinFunctions(t *testing.T) {
{`first([1, 2, 3])`, 1},
{`first([])`, object.NULL},
{`first(1)`,
- &object.Error{
+ object.Error{
Message: "TypeError: first() expected argument #1 to be `array` got `int`",
},
},
{`last([1, 2, 3])`, 3},
{`last([])`, object.NULL},
{`last(1)`,
- &object.Error{
+ object.Error{
Message: "TypeError: last() expected argument #1 to be `array` got `int`",
},
},
@@ -743,12 +741,12 @@ func TestBuiltinFunctions(t *testing.T) {
{`rest([])`, []int{}},
{`push([], 1)`, []int{1}},
{`push(1, 1)`,
- &object.Error{
+ object.Error{
Message: "TypeError: push() expected argument #1 to be `array` got `int`",
},
},
{`input()`, ""},
- {`pop([])`, &object.Error{
+ {`pop([])`, object.Error{
Message: "IndexError: pop from an empty array",
},
},
diff --git a/monkey.go b/monkey.go
index ee505c3..ec59632 100644
--- a/monkey.go
+++ b/monkey.go
@@ -99,6 +99,10 @@ func ExecFile(fn string, opts *Options) error {
err error
)
+ if opts == nil {
+ opts = &Options{}
+ }
+
ext := filepath.Ext(fn)
mc := fn[:len(fn)-len(ext)] + ".mc"
@@ -128,7 +132,7 @@ func ExecFile(fn string, opts *Options) error {
}
}
- mvm := vm.New(fn, bytecode, vm.WithDebug(opts.Debug), vm.WithTrace(opts.Trace))
+ mvm := vm.New(fn, bytecode, opts.ToVMOptions()...)
if err := mvm.Run(); err != nil {
return err
}
@@ -138,6 +142,10 @@ func ExecFile(fn string, opts *Options) error {
// ExecString executes a Monkey program from a string.
func ExecString(input string, opts *Options) error {
+ if opts == nil {
+ opts = &Options{}
+ }
+
bytecode, err := compileString(input, opts.Debug)
if err != nil {
return err
@@ -157,6 +165,10 @@ func ExecString(input string, opts *Options) error {
// CompileFiles compiles multiple Monkey files and returns any errors encountered during compilation.
func CompileFiles(fns []string, opts *Options) error {
+ if opts == nil {
+ opts = &Options{}
+ }
+
for _, fn := range fns {
ext := filepath.Ext(fn)
mc := fn[:len(fn)-len(ext)] + ".mc"
diff --git a/options.go b/options.go
index 9ad87e7..f4d5e82 100644
--- a/options.go
+++ b/options.go
@@ -1,23 +1,38 @@
package monkey
import (
- "context"
"io"
+ "monkey/internal/context"
"monkey/internal/vm"
+ "os"
)
+// Options represents the options for running a script with Monkey.
type Options struct {
- Debug bool
- Trace bool
+ Debug bool // Whether to enable debug mode or not
+ Trace bool // Whether to enable trace mode or not
- Args []string
- Stdin io.Reader
- Stdout io.Writer
- Stderr io.Writer
- Exit func(int)
+ Args []string // The arguments passed to the script
+ Stdin io.Reader // The input reader for the script
+ Stdout io.Writer // The output writer for the script
+ Stderr io.Writer // The error writer for the script
+ Exit func(int) // A function to call when the script exits
}
func (opts Options) ToVMOptions() []vm.Option {
+ if opts.Stdin == nil {
+ opts.Stdin = os.Stdin
+ }
+ if opts.Stdout == nil {
+ opts.Stdout = os.Stdout
+ }
+ if opts.Stderr == nil {
+ opts.Stderr = os.Stderr
+ }
+ if opts.Exit == nil {
+ opts.Exit = os.Exit
+ }
+
ctx := context.New(
context.WithArgs(opts.Args),
context.WithStdin(opts.Stdin),
@@ -28,7 +43,7 @@ func (opts Options) ToVMOptions() []vm.Option {
return []vm.Option{
vm.WithDebug(opts.Debug),
- vm.WithDebug(opts.Trace),
+ vm.WithTrace(opts.Trace),
vm.WithContext(ctx),
}
}
diff --git a/repl.go b/repl.go
index 9c352ee..24a0d48 100644
--- a/repl.go
+++ b/repl.go
@@ -6,7 +6,6 @@ import (
"github.com/tebeka/atexit"
"io"
"log"
- "monkey/internal/context"
"os"
"strings"
@@ -22,7 +21,10 @@ import (
// by lexing, parsing and evaluating the input in the interpreter
// REPL provides a read-eval-print loop for the monkey virtual machine.
-func REPL(args []string, opts *Options) error {
+func REPL(opts *Options) error {
+ if opts == nil {
+ opts = &Options{}
+ }
initState, err := term.MakeRaw(0)
if err != nil {
@@ -42,12 +44,7 @@ func REPL(args []string, opts *Options) error {
t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete
- ctx := context.New(
- context.WithArgs(args),
- context.WithStdout(t),
- context.WithStderr(t),
- context.WithExit(atexit.Exit),
- )
+ opts.Exit = atexit.Exit
state := vm.NewState()
@@ -86,14 +83,7 @@ func REPL(args []string, opts *Options) error {
log.Printf("Bytecode:\n%s\n", c.Bytecode())
}
- opts := []vm.Option{
- vm.WithContext(ctx),
- vm.WithDebug(opts.Debug),
- vm.WithTrace(opts.Trace),
- vm.WithState(state),
- }
-
- mvm := vm.New("
", c.Bytecode(), opts...)
+ mvm := vm.New("", c.Bytecode(), opts.ToVMOptions()...)
if err := mvm.Run(); err != nil {
fmt.Fprintf(t, "runtime error: %v\n", err)
@@ -152,8 +142,12 @@ func acceptUntil(t *term.Terminal, start, end string) (string, error) {
}
// SimpleREPL provides a simple read-eval-print loop for the monkey virtual machine.
-func SimpleREPL(args []string, opts *Options) {
- var reader = bufio.NewReader(os.Stdin)
+func SimpleREPL(opts *Options) {
+ if opts == nil {
+ opts = &Options{}
+ }
+
+ reader := bufio.NewReader(os.Stdin)
defer func() {
if err := recover(); err != nil {
@@ -164,13 +158,6 @@ func SimpleREPL(args []string, opts *Options) {
t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete
- ctx := context.New(
- context.WithArgs(args),
- context.WithStdout(t),
- context.WithStderr(t),
- context.WithExit(atexit.Exit),
- )
-
state := vm.NewState()
PrintVersionInfo(os.Stdout)
@@ -209,14 +196,7 @@ func SimpleREPL(args []string, opts *Options) {
log.Printf("Bytecode:\n%s\n", c.Bytecode())
}
- opts := []vm.Option{
- vm.WithContext(ctx),
- vm.WithDebug(opts.Debug),
- vm.WithTrace(opts.Trace),
- vm.WithState(state),
- }
-
- mvm := vm.New("", c.Bytecode(), opts...)
+ mvm := vm.New("", c.Bytecode(), opts.ToVMOptions()...)
if err := mvm.Run(); err != nil {
fmt.Printf("runtime error: %v\n", err)