From f9e6e164b0ec8f237fc9e72c56c0799077afd036 Mon Sep 17 00:00:00 2001 From: csmith Date: Mon, 1 Apr 2024 17:02:44 -0400 Subject: [PATCH] further improvements --- .gitignore | 3 +- .idea/.gitignore | 8 --- .idea/git_toolbox_prj.xml | 15 ----- .idea/vcs.xml | 2 +- Benchmark.md | 10 ++-- cmd/monkey/main.go | 10 ++-- compare.sh | 24 ++++++++ go.mod | 1 - go.sum | 15 +---- internal/builtins/bind.go | 2 +- internal/builtins/builtins.go | 4 +- internal/builtins/close.go | 2 +- internal/builtins/ffi.go | 2 +- internal/builtins/id.go | 8 +-- internal/builtins/listen.go | 2 +- internal/builtins/writefile.go | 2 +- internal/evaluator/evaluator.go | 8 +-- internal/evaluator/evaluator_test.go | 2 +- internal/object/bool.go | 12 ++++ internal/object/builtin.go | 8 +-- internal/object/int.go | 29 +++++---- internal/object/null.go | 2 + internal/vm/vm.go | 88 ++++++++++++++++------------ internal/vm/vm_test.go | 36 ++++++------ monkey.go | 3 +- repl.go | 8 ++- 26 files changed, 168 insertions(+), 138 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/git_toolbox_prj.xml create mode 100644 compare.sh diff --git a/.gitignore b/.gitignore index 4025deb..a3a52e1 100644 --- a/.gitignore +++ b/.gitignore @@ -92,4 +92,5 @@ fabric.properties /dist /.vscode /monkey -/examples/fib \ No newline at end of file +/examples/fib +vendor \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml deleted file mode 100644 index 02b915b..0000000 --- a/.idea/git_toolbox_prj.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/Benchmark.md b/Benchmark.md index 0e0aa26..bdf34dd 100644 --- a/Benchmark.md +++ b/Benchmark.md @@ -1,7 +1,7 @@ | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | |:---|---:|---:|---:|---:| -| `c` | 36.7 ± 0.6 | 36.0 | 37.8 | 1.00 | -| `go` | 230.2 ± 14.0 | 206.2 | 252.9 | 6.28 ± 0.40 | -| `python` | 1366.9 ± 30.8 | 1330.9 | 1411.3 | 37.27 ± 1.06 | -| `tengo` | 1704.3 ± 16.0 | 1687.2 | 1739.3 | 46.47 ± 0.91 | -| `monkey` | 3347.6 ± 22.3 | 3314.0 | 3396.0 | 91.27 ± 1.69 | +| `c` | 37.4 ± 1.1 | 36.2 | 39.4 | 1.00 | +| `go` | 168.5 ± 27.2 | 120.2 | 202.6 | 4.51 ± 0.74 | +| `python` | 1346.8 ± 8.2 | 1335.7 | 1361.6 | 36.05 ± 1.09 | +| `tengo` | 1708.6 ± 15.9 | 1686.2 | 1731.4 | 45.74 ± 1.43 | +| `monkey` | 2148.7 ± 6.6 | 2135.6 | 2157.0 | 57.52 ± 1.72 | diff --git a/cmd/monkey/main.go b/cmd/monkey/main.go index 4463234..692db03 100644 --- a/cmd/monkey/main.go +++ b/cmd/monkey/main.go @@ -13,12 +13,14 @@ func main() { version bool simple bool debug bool + trace bool ) flag.BoolVar(&compile, "c", false, "Compile a monkey file into a '.mc' 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.BoolVar(&debug, "d", false, "Enable debug mode") + flag.BoolVar(&debug, "D", false, "Enable Compiler and VM debugging") + flag.BoolVar(&trace, "T", false, "Enable VM tracing") flag.Parse() switch { @@ -29,12 +31,12 @@ func main() { monkey.PrintVersionInfo(os.Stdout) case flag.NArg() > 0: - monkey.ExecFileVM(flag.Arg(0), flag.Args()[1:], debug) + monkey.ExecFileVM(flag.Arg(0), flag.Args()[1:], debug, trace) case simple: - monkey.SimpleVmREPL(flag.Args(), debug) + monkey.SimpleVmREPL(flag.Args(), debug, trace) default: - monkey.VmREPL(flag.Args(), debug) + monkey.VmREPL(flag.Args(), debug, trace) } } diff --git a/compare.sh b/compare.sh new file mode 100644 index 0000000..021edcf --- /dev/null +++ b/compare.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [ $# -ne 1 ]; then + echo "Usage: $(basename "$0") " + exit 1 +fi + +compareWith="$1" +currentBranch="$(git branch --show-current)" + +if [ -n "$(git status --porcelain)" ]; then + echo "$currentBranch is not clean, please stash or commit your changes!" + exit 1 +fi + +hyperfine \ + -w 5 \ + -c "git checkout $currentBranch" \ + -p "make build" \ + -p "git checkout $compareWith; make build" \ + -n "$currentBranch" \ + -n "$compareWith" \ + './monkey examples/fib.monkey' \ + './monkey examples/fib.monkey' \ No newline at end of file diff --git a/go.mod b/go.mod index 557e906..62cf442 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module monkey go 1.21 require ( - github.com/pkg/profile v1.7.0 github.com/stretchr/testify v1.8.4 github.com/tebeka/atexit v0.3.0 golang.org/x/term v0.18.0 diff --git a/go.sum b/go.sum index 4471a89..8ef982f 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,20 @@ -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= -github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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/tebeka/atexit v0.3.0 h1:jleL99H7Ywt80oJKR+VWmJNnezcCOG0CuzcN3CIpsdI= github.com/tebeka/atexit v0.3.0/go.mod h1:WJmSUSmMT7WoR7etUOaGBVXk+f5/ZJ+67qwuedq7Fbs= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/builtins/bind.go b/internal/builtins/bind.go index 90483c5..641673b 100644 --- a/internal/builtins/bind.go +++ b/internal/builtins/bind.go @@ -50,5 +50,5 @@ func Bind(args ...object.Object) object.Object { return newError("SocketError: %s", err) } - return object.Null{} + return object.NULL } diff --git a/internal/builtins/builtins.go b/internal/builtins/builtins.go index 26e5d27..d0e271b 100644 --- a/internal/builtins/builtins.go +++ b/internal/builtins/builtins.go @@ -7,7 +7,7 @@ import ( ) // Builtins ... -var Builtins = map[string]*object.Builtin{ +var Builtins = map[string]object.Builtin{ "len": {Name: "len", Fn: Len}, "input": {Name: "input", Fn: Input}, "print": {Name: "print", Fn: Print}, @@ -58,7 +58,7 @@ var Builtins = map[string]*object.Builtin{ } // BuiltinsIndex ... -var BuiltinsIndex []*object.Builtin +var BuiltinsIndex []object.Builtin func init() { var keys []string diff --git a/internal/builtins/close.go b/internal/builtins/close.go index 6f72b44..eb303c0 100644 --- a/internal/builtins/close.go +++ b/internal/builtins/close.go @@ -23,5 +23,5 @@ func Close(args ...object.Object) object.Object { return newError("IOError: %s", err) } - return object.Null{} + return object.NULL } diff --git a/internal/builtins/ffi.go b/internal/builtins/ffi.go index d18a552..1f5d158 100644 --- a/internal/builtins/ffi.go +++ b/internal/builtins/ffi.go @@ -33,5 +33,5 @@ func FFI(args ...object.Object) object.Object { return newError("error finding symbol: %s", err) } - return &object.Builtin{Name: symbol, Fn: v.(object.BuiltinFunction)} + return object.Builtin{Name: symbol, Fn: v.(object.BuiltinFunction)} } diff --git a/internal/builtins/id.go b/internal/builtins/id.go index 5df86e8..e479719 100644 --- a/internal/builtins/id.go +++ b/internal/builtins/id.go @@ -30,11 +30,11 @@ func IdOf(args ...object.Object) object.Object { } else if h, ok := arg.(*object.Hash); ok { return object.String{Value: fmt.Sprintf("%p", h)} } else if f, ok := arg.(*object.Function); ok { - return object.String{Value: fmt.Sprintf("%p", f)} + return object.String{Value: fmt.Sprintf("%p", &f)} } else if c, ok := arg.(*object.Closure); ok { - return object.String{Value: fmt.Sprintf("%p", c)} - } else if b, ok := arg.(*object.Builtin); ok { - return object.String{Value: fmt.Sprintf("%p", b)} + return object.String{Value: fmt.Sprintf("%p", &c)} + } else if b, ok := arg.(object.Builtin); ok { + return object.String{Value: fmt.Sprintf("%p", &b)} } return nil } diff --git a/internal/builtins/listen.go b/internal/builtins/listen.go index a322dad..206b7bb 100644 --- a/internal/builtins/listen.go +++ b/internal/builtins/listen.go @@ -23,5 +23,5 @@ func Listen(args ...object.Object) object.Object { return newError("SocketError: %s", err) } - return object.Null{} + return object.NULL } diff --git a/internal/builtins/writefile.go b/internal/builtins/writefile.go index 8fd385b..8339dfa 100644 --- a/internal/builtins/writefile.go +++ b/internal/builtins/writefile.go @@ -24,5 +24,5 @@ func WriteFile(args ...object.Object) object.Object { return newError("IOError: error writing file %s: %s", filename, err) } - return object.Null{} + return object.NULL } diff --git a/internal/evaluator/evaluator.go b/internal/evaluator/evaluator.go index ba4937e..75752b2 100644 --- a/internal/evaluator/evaluator.go +++ b/internal/evaluator/evaluator.go @@ -126,7 +126,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { case *ast.FunctionLiteral: params := node.Parameters body := node.Body - return &object.Function{Parameters: params, Env: env, Body: body} + return object.Function{Parameters: params, Env: env, Body: body} // Expressions case *ast.IntegerLiteral: @@ -560,12 +560,12 @@ func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Ob func applyFunction(fn object.Object, args []object.Object) object.Object { switch fn := fn.(type) { - case *object.Function: + case object.Function: extendedEnv := extendFunctionEnv(fn, args) evaluated := Eval(fn.Body, extendedEnv) return unwrapReturnValue(evaluated) - case *object.Builtin: + case object.Builtin: if result := fn.Fn(args...); result != nil { return result } @@ -577,7 +577,7 @@ func applyFunction(fn object.Object, args []object.Object) object.Object { } } -func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment { +func extendFunctionEnv(fn object.Function, args []object.Object) *object.Environment { env := object.NewEnclosedEnvironment(fn.Env) for paramIdx, param := range fn.Parameters { diff --git a/internal/evaluator/evaluator_test.go b/internal/evaluator/evaluator_test.go index 3e10172..6260be7 100644 --- a/internal/evaluator/evaluator_test.go +++ b/internal/evaluator/evaluator_test.go @@ -354,7 +354,7 @@ func TestFunctionObject(t *testing.T) { input := "fn(x) { x + 2; };" evaluated := testEval(input) - fn, ok := evaluated.(*object.Function) + fn, ok := evaluated.(object.Function) if !ok { t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated) } diff --git a/internal/object/bool.go b/internal/object/bool.go index 0948706..7c06f35 100644 --- a/internal/object/bool.go +++ b/internal/object/bool.go @@ -4,10 +4,22 @@ import ( "fmt" ) +var ( + TRUE = Boolean{Value: true} + FALSE = Boolean{Value: false} +) + type Boolean struct { Value bool } +func NewBoolean(value bool) Boolean { + if value { + return TRUE + } + return FALSE +} + func (b Boolean) Bool() bool { return b.Value } diff --git a/internal/object/builtin.go b/internal/object/builtin.go index c6afbd5..6fdccc8 100644 --- a/internal/object/builtin.go +++ b/internal/object/builtin.go @@ -7,18 +7,18 @@ type Builtin struct { Fn BuiltinFunction } -func (b *Builtin) Bool() bool { +func (b Builtin) Bool() bool { return true } -func (b *Builtin) Type() Type { +func (b Builtin) Type() Type { return BuiltinType } -func (b *Builtin) Inspect() string { +func (b Builtin) Inspect() string { return fmt.Sprintf("", b.Name) } -func (b *Builtin) String() string { +func (b Builtin) String() string { return b.Inspect() } diff --git a/internal/object/int.go b/internal/object/int.go index 5a7c06e..291c188 100644 --- a/internal/object/int.go +++ b/internal/object/int.go @@ -27,17 +27,21 @@ func (i Integer) String() string { } func (i Integer) Add(other Object) (Object, error) { - if !AssertTypes(other, IntegerType) { + switch obj := other.(type) { + case Integer: + return Integer{i.Value + obj.Value}, nil + default: return nil, NewBinaryOpError(i, other, "+") } - return Integer{i.Value + other.(Integer).Value}, nil } func (i Integer) Sub(other Object) (Object, error) { - if !AssertTypes(other, IntegerType) { + switch obj := other.(type) { + case Integer: + return Integer{i.Value - obj.Value}, nil + default: return nil, NewBinaryOpError(i, other, "-") } - return Integer{i.Value - other.(Integer).Value}, nil } func (i Integer) Mul(other Object) (Object, error) { @@ -115,15 +119,16 @@ func (i Integer) Negate() Object { } func (i Integer) Compare(other Object) int { - if obj, ok := other.(Integer); ok { - switch { - case i.Value < obj.Value: + switch obj := other.(type) { + case Integer: + if i.Value < obj.Value { return -1 - case i.Value > obj.Value: - return 1 - default: - return 0 } + if i.Value > obj.Value { + return 1 + } + return 0 + default: + return -1 } - return -1 } diff --git a/internal/object/null.go b/internal/object/null.go index 50a6a92..2111ce0 100644 --- a/internal/object/null.go +++ b/internal/object/null.go @@ -1,5 +1,7 @@ package object +var NULL = Null{} + type Null struct{} func (n Null) Bool() bool { diff --git a/internal/vm/vm.go b/internal/vm/vm.go index 9c0a19d..9ba86ee 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -12,6 +12,7 @@ import ( "monkey/internal/utils" "os" "path/filepath" + "sort" "time" "unicode" ) @@ -20,10 +21,6 @@ const StackSize = 2048 const GlobalsSize = 65536 const MaxFrames = 1024 -var Null = object.Null{} -var True = object.Boolean{Value: true} -var False = object.Boolean{Value: false} - func isTruthy(obj object.Object) bool { switch obj := obj.(type) { @@ -114,6 +111,7 @@ func (s *VMState) ExportedHash() *object.Hash { type VM struct { Debug bool + Trace bool state *VMState @@ -143,9 +141,9 @@ func (vm *VM) popFrame() Frame { // New constructs a new monkey-lang bytecode virtual machine func New(fn string, bytecode *compiler.Bytecode) *VM { - mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} - mainClosure := &object.Closure{Fn: mainFn} - mainFrame := NewFrame(mainClosure, 0) + mainFn := object.CompiledFunction{Instructions: bytecode.Instructions} + mainClosure := object.Closure{Fn: &mainFn} + mainFrame := NewFrame(&mainClosure, 0) frames := make([]Frame, MaxFrames) frames[0] = mainFrame @@ -169,9 +167,9 @@ func New(fn string, bytecode *compiler.Bytecode) *VM { } func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM { - mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions} - mainClosure := &object.Closure{Fn: mainFn} - mainFrame := NewFrame(mainClosure, 0) + mainFn := object.CompiledFunction{Instructions: bytecode.Instructions} + mainClosure := object.Closure{Fn: &mainFn} + mainFrame := NewFrame(&mainClosure, 0) frames := make([]Frame, MaxFrames) frames[0] = mainFrame @@ -222,13 +220,13 @@ func (vm *VM) executeConstant() error { func (vm *VM) executeAssignGlobal() error { globalIndex := vm.currentFrame().ReadUint16() vm.state.Globals[globalIndex] = vm.pop() - return vm.push(Null) + return vm.push(object.NULL) } func (vm *VM) executeAssignLocal() error { localIndex := vm.currentFrame().ReadUint8() vm.stack[vm.currentFrame().basePointer+int(localIndex)] = vm.pop() - return vm.push(Null) + return vm.push(object.NULL) } func (vm *VM) executeSetGlobal() error { @@ -241,7 +239,7 @@ func (vm *VM) executeSetGlobal() error { vm.state.Globals[globalIndex] = ref } - return vm.push(Null) + return vm.push(object.NULL) } func (vm *VM) executeGetGlobal() error { @@ -259,7 +257,7 @@ func (vm *VM) executeSetLocal() error { vm.stack[vm.currentFrame().basePointer+int(localIndex)] = ref } - return vm.push(Null) + return vm.push(object.NULL) } func (vm *VM) executeGetLocal() error { @@ -283,15 +281,15 @@ func (vm *VM) executeCurrentClosure() error { } func (vm *VM) executeTrue() error { - return vm.push(True) + return vm.push(object.TRUE) } func (vm *VM) executeFalse() error { - return vm.push(False) + return vm.push(object.FALSE) } func (vm *VM) executeNull() error { - return vm.push(Null) + return vm.push(object.NULL) } func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) { @@ -547,9 +545,9 @@ func (vm *VM) executeEqual() error { if obj, ok := left.(object.Comparable); ok { val := obj.Compare(right) if val == 0 { - return vm.push(True) + return vm.push(object.TRUE) } - return vm.push(False) + return vm.push(object.FALSE) } return object.NewBinaryOpError(left, right, "==") @@ -562,9 +560,9 @@ func (vm *VM) executeNotEqual() error { if obj, ok := left.(object.Comparable); ok { val := obj.Compare(right) if val != 0 { - return vm.push(True) + return vm.push(object.TRUE) } - return vm.push(False) + return vm.push(object.FALSE) } return object.NewBinaryOpError(left, right, "!=") @@ -577,9 +575,9 @@ func (vm *VM) executeGreaterThan() error { if obj, ok := left.(object.Comparable); ok { val := obj.Compare(right) if val == 1 { - return vm.push(True) + return vm.push(object.TRUE) } - return vm.push(False) + return vm.push(object.FALSE) } return object.NewBinaryOpError(left, right, ">") @@ -592,9 +590,9 @@ func (vm *VM) executeGreaterThanOrEqual() error { if obj, ok := left.(object.Comparable); ok { val := obj.Compare(right) if val >= 0 { - return vm.push(True) + return vm.push(object.TRUE) } - return vm.push(False) + return vm.push(object.FALSE) } return object.NewBinaryOpError(left, right, ">") @@ -630,7 +628,7 @@ func (vm *VM) executeSetItem() error { if err != nil { return err } - return vm.push(Null) + return vm.push(object.NULL) } return fmt.Errorf( @@ -664,7 +662,7 @@ func (vm *VM) executeCall() error { switch callee := callee.(type) { case *object.Closure: return vm.callClosure(callee, args) - case *object.Builtin: + case object.Builtin: return vm.callBuiltin(callee, args) default: return fmt.Errorf( @@ -727,7 +725,7 @@ func (vm *VM) callClosure(cl *object.Closure, numArgs int) error { return nil } -func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error { +func (vm *VM) callBuiltin(builtin object.Builtin, numArgs int) error { args := vm.stack[vm.sp-numArgs : vm.sp] result := builtin.Fn(args...) @@ -739,7 +737,7 @@ func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error { return err } } else { - err := vm.push(Null) + err := vm.push(object.NULL) if err != nil { return err } @@ -761,8 +759,8 @@ func (vm *VM) pushClosure(constIndex, numFree int) error { } vm.sp = vm.sp - numFree - closure := &object.Closure{Fn: function, Free: free} - return vm.push(closure) + closure := object.Closure{Fn: function, Free: free} + return vm.push(&closure) } func (vm *VM) loadModule(name object.Object) error { @@ -788,20 +786,38 @@ func (vm *VM) LastPoppedStackElem() object.Object { } func (vm *VM) Run() (err error) { - var n int + var ( + op code.Opcode + opcodeFreqs = make(map[code.Opcode]int) + ) if vm.Debug { start := time.Now() defer func() { - log.Printf("%d instructions executeuted in %s", n, time.Now().Sub(start)) + total := 0 + opcodes := make([]code.Opcode, 0, len(opcodeFreqs)) + + for opcode := range opcodeFreqs { + opcodes = append(opcodes, opcode) + } + + sort.SliceStable(opcodes, func(i, j int) bool { + return opcodeFreqs[opcodes[i]] > opcodeFreqs[opcodes[j]] + }) + + log.Printf("%d instructions executed in %s", total, time.Now().Sub(start)) + log.Print("Top 10 instructions:") + for _, opcode := range opcodes[:10] { + log.Printf("%10d %s", opcodeFreqs[opcode], opcode) + } }() } for err == nil { - op := vm.currentFrame().ReadNextOp() + op = vm.currentFrame().ReadNextOp() if vm.Debug { - n++ + opcodeFreqs[op]++ log.Printf( "%-25s %-20s\n", fmt.Sprintf("%04d %s", vm.currentFrame().ip, op), @@ -949,7 +965,7 @@ func (vm *VM) Run() (err error) { err = fmt.Errorf("unhandled opcode: %s", op) } - if vm.Debug { + if vm.Trace { log.Printf( "%-25s [ip=%02d fp=%02d, sp=%02d]", "", vm.currentFrame().ip, vm.fp-1, vm.sp, diff --git a/internal/vm/vm_test.go b/internal/vm/vm_test.go index a740951..b0b32b5 100644 --- a/internal/vm/vm_test.go +++ b/internal/vm/vm_test.go @@ -130,7 +130,7 @@ func testExpectedObject(t *testing.T, expected interface{}, actual object.Object } case object.Null: - if actual != Null { + if actual != object.NULL { t.Errorf("object is not Null: %T (%+v)", actual, actual) } @@ -302,14 +302,14 @@ func TestConditionals(t *testing.T) { {"if (1 < 2) { 10 }", 10}, {"if (1 < 2) { 10 } else { 20 }", 10}, {"if (1 > 2) { 10 } else { 20 }", 20}, - {"if (1 > 2) { 10 }", Null}, - {"if (false) { 10 }", Null}, + {"if (1 > 2) { 10 }", object.NULL}, + {"if (false) { 10 }", object.NULL}, {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, - {"if (true) { a := 5; }", Null}, - {"if (true) { 10; a := 5; }", Null}, - {"if (false) { 10 } else { b := 5; }", Null}, - {"if (false) { 10 } else { 10; b := 5; }", Null}, - {"if (true) { a := 5; } else { 10 }", Null}, + {"if (true) { a := 5; }", object.NULL}, + {"if (true) { 10; a := 5; }", object.NULL}, + {"if (false) { 10 } else { b := 5; }", object.NULL}, + {"if (false) { 10 } else { 10; b := 5; }", object.NULL}, + {"if (true) { a := 5; } else { 10 }", object.NULL}, {"x := 0; if (true) { x = 1; }; if (false) { x = 2; }; x", 1}, {"if (1 < 2) { 10 } else if (1 == 2) { 20 }", 10}, {"if (1 > 2) { 10 } else if (1 == 2) { 20 } else { 30 }", 30}, @@ -430,13 +430,13 @@ func TestIndexExpressions(t *testing.T) { {"[1, 2, 3][1]", 2}, {"[1, 2, 3][0 + 2]", 3}, {"[[1, 1, 1]][0][0]", 1}, - {"[][0]", Null}, - {"[1, 2, 3][99]", Null}, - {"[1][-1]", Null}, + {"[][0]", object.NULL}, + {"[1, 2, 3][99]", object.NULL}, + {"[1][-1]", object.NULL}, {"{1: 1, 2: 2}[1]", 1}, {"{1: 1, 2: 2}[2]", 2}, - {"{1: 1}[0]", Null}, - {"{}[0]", Null}, + {"{1: 1}[0]", object.NULL}, + {"{}[0]", object.NULL}, {`"abc"[0]`, "a"}, {`"abc"[1]`, "b"}, {`"abc"[2]`, "c"}, @@ -506,7 +506,7 @@ func TestFunctionsWithoutReturnValue(t *testing.T) { noReturn := fn() { }; noReturn(); `, - expected: Null, + expected: object.NULL, }, { input: ` @@ -515,7 +515,7 @@ func TestFunctionsWithoutReturnValue(t *testing.T) { noReturn(); noReturnTwo(); `, - expected: Null, + expected: object.NULL, }, } @@ -724,16 +724,16 @@ func TestBuiltinFunctions(t *testing.T) { }, {`len([1, 2, 3])`, 3}, {`len([])`, 0}, - {`print("hello", "world!")`, Null}, + {`print("hello", "world!")`, object.NULL}, {`first([1, 2, 3])`, 1}, - {`first([])`, Null}, + {`first([])`, object.NULL}, {`first(1)`, &object.Error{ Message: "TypeError: first() expected argument #1 to be `array` got `int`", }, }, {`last([1, 2, 3])`, 3}, - {`last([])`, Null}, + {`last([])`, object.NULL}, {`last(1)`, &object.Error{ Message: "TypeError: last() expected argument #1 to be `array` got `int`", diff --git a/monkey.go b/monkey.go index 53596e1..3c87f82 100644 --- a/monkey.go +++ b/monkey.go @@ -71,7 +71,7 @@ func compile(path string, debug bool) (bc *compiler.Bytecode, err error) { return c.Bytecode(), nil } -func ExecFileVM(f string, args []string, debug bool) (err error) { +func ExecFileVM(f string, args []string, debug, trace bool) (err error) { var bytecode *compiler.Bytecode object.Args = args @@ -89,6 +89,7 @@ func ExecFileVM(f string, args []string, debug bool) (err error) { mvm := vm.New(f, bytecode) mvm.Debug = debug + mvm.Trace = trace if err = mvm.Run(); err != nil { fmt.Println(err) return diff --git a/repl.go b/repl.go index 5378e45..390753c 100644 --- a/repl.go +++ b/repl.go @@ -20,7 +20,7 @@ import ( // Package repl implements the Read-Eval-Print-Loop or interactive console // by lexing, parsing and evaluating the input in the interpreter -func VmREPL(args []string, debug bool) error { +func VmREPL(args []string, debug, trace bool) error { var state = vm.NewVMState() object.Args = args @@ -73,6 +73,8 @@ func VmREPL(args []string, debug bool) error { mvm := vm.NewWithState("", c.Bytecode(), state) mvm.Debug = debug + mvm.Trace = trace + if err := mvm.Run(); err != nil { fmt.Fprintf(t, "runtime error: %v\n", err) continue @@ -129,7 +131,7 @@ func acceptUntil(t *term.Terminal, start, end string) (string, error) { return buf.String(), nil } -func SimpleVmREPL(args []string, debug bool) { +func SimpleVmREPL(args []string, debug, trace bool) { var ( state = vm.NewVMState() reader = bufio.NewReader(os.Stdin) @@ -171,6 +173,8 @@ func SimpleVmREPL(args []string, debug bool) { mvm := vm.NewWithState("", c.Bytecode(), state) mvm.Debug = debug + mvm.Trace = trace + if err := mvm.Run(); err != nil { fmt.Printf("runtime error: %v\n", err) continue