diff --git a/.chglog/config.yml b/.chglog/config.yml index 9cd27d2..03ee4b3 100644 --- a/.chglog/config.yml +++ b/.chglog/config.yml @@ -3,7 +3,7 @@ style: Github template: CHANGELOG.tpl.md info: title: CHANGELOG - repository_url: https://go.unflavoredmeson.com/monkey + repository_url: https://git.unflavoredmeson.com/monkey options: commits: filters: diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..de2ef6f --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,33 @@ +--- + +name: Publish Image + +on: + push: + branches: [ master ] + +env: + REGISTRY: r.unflavoredmeson.com + IMAGE: unflavoredmeson/monkey + TAG: latest + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Setup Docker Buildx + uses: actions/setup-buildx@v2 + - name: Login to Registry + uses: actions/docker-login@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASS }} + - name: Build and Push Image + uses: actions/docker-build-push@v4 + with: + context: . + push: true + tags: ${{ env.REGISTRY}}/${{ env.IMAGE }}:${{ env.TAG }} \ No newline at end of file diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml new file mode 100644 index 0000000..02b915b --- /dev/null +++ b/.idea/git_toolbox_prj.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/cmd/monkey/main.go b/cmd/monkey/main.go index d56a882..d068a95 100644 --- a/cmd/monkey/main.go +++ b/cmd/monkey/main.go @@ -3,6 +3,7 @@ package main import ( "flag" + "fmt" "monkey" "os" ) @@ -24,6 +25,7 @@ func main() { flag.Parse() opts := &monkey.Options{ + Args: flag.Args(), Debug: debug, Trace: trace, } @@ -37,12 +39,15 @@ func main() { case flag.NArg() > 0: opts.Args = flag.Args()[1:] - monkey.ExecFile(flag.Arg(0), opts) + if err := monkey.ExecFile(flag.Arg(0), opts); err != nil { + fmt.Fprintf(os.Stderr, "runtime error: %v\n", err) + os.Exit(1) + } case simple: - monkey.SimpleREPL(flag.Args(), opts) + monkey.SimpleREPL(opts) default: - monkey.REPL(flag.Args(), opts) + monkey.REPL(opts) } } diff --git a/examples/embed.go b/examples/embed.go index 64bf717..4e36892 100644 --- a/examples/embed.go +++ b/examples/embed.go @@ -4,12 +4,13 @@ package main import ( "log" + "monkey" ) const program = `println("Hello World!")` func main() { - if err := monkey.ExecString(program, false, false); err != nil { + if err := monkey.ExecString(program, nil); err != nil { log.Fatal("error running program") } } diff --git a/internal/builtins/find.go b/internal/builtins/find.go index 4b6c6c8..3b7cf8b 100644 --- a/internal/builtins/find.go +++ b/internal/builtins/find.go @@ -36,7 +36,7 @@ func Find(ctx context.Context, args ...object.Object) object.Object { // find in array if haystack, ok := args[0].(*object.Array); ok { - needle := args[1].(object.Comparable) + needle := args[1] i := sort.Search(len(haystack.Elements), func(i int) bool { return needle.Compare(haystack.Elements[i]) == 0 }) diff --git a/internal/evaluator/evaluator.go b/internal/evaluator/evaluator.go index 7db93a4..c662304 100644 --- a/internal/evaluator/evaluator.go +++ b/internal/evaluator/evaluator.go @@ -26,37 +26,37 @@ func isError(obj object.Object) bool { return false } -func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Object { +func Eval(ctx context.Context, node ast.Node, env *object.Environment) object.Object { switch node := node.(type) { // Statements case *ast.Program: - return evalProgram(node, ctx, env) + return evalProgram(ctx, node, env) case *ast.ExpressionStatement: - return Eval(node.Expression, ctx, env) + return Eval(ctx, node.Expression, env) case *ast.BlockStatement: - return evalBlockStatements(node, ctx, env) + return evalBlockStatements(ctx, node, env) case *ast.IfExpression: - return evalIfExpression(node, ctx, env) + return evalIfExpression(ctx, node, env) case *ast.WhileExpression: - return evalWhileExpression(node, ctx, env) + return evalWhileExpression(ctx, node, env) case *ast.ImportExpression: - return evalImportExpression(node, ctx, env) + return evalImportExpression(ctx, node, env) case *ast.ReturnStatement: - val := Eval(node.ReturnValue, ctx, env) + val := Eval(ctx, node.ReturnValue, env) if isError(val) { return val } return object.ReturnValue{Value: val} case *ast.BindExpression: - value := Eval(node.Value, ctx, env) + value := Eval(ctx, node.Value, env) if isError(value) { return value } @@ -73,12 +73,12 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob return newError("expected identifier on left got=%T", node.Left) case *ast.AssignmentExpression: - left := Eval(node.Left, ctx, env) + left := Eval(ctx, node.Left, env) if isError(left) { return left } - value := Eval(node.Value, ctx, env) + value := Eval(ctx, node.Value, env) if isError(value) { return value } @@ -86,13 +86,13 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob if ident, ok := node.Left.(*ast.Identifier); ok { env.Set(ident.Value, value) } else if ie, ok := node.Left.(*ast.IndexExpression); ok { - obj := Eval(ie.Left, ctx, env) + obj := Eval(ctx, ie.Left, env) if isError(obj) { return obj } if array, ok := obj.(*object.Array); ok { - index := Eval(ie.Index, ctx, env) + index := Eval(ctx, ie.Index, env) if isError(index) { return index } @@ -102,7 +102,7 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob return newError("cannot index array with %#v", index) } } else if hash, ok := obj.(*object.Hash); ok { - key := Eval(ie.Index, ctx, env) + key := Eval(ctx, ie.Index, env) if isError(key) { return key } @@ -140,29 +140,29 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob return NULL case *ast.PrefixExpression: - right := Eval(node.Right, ctx, env) + right := Eval(ctx, node.Right, env) if isError(right) { return right } return evalPrefixExpression(node.Operator, right) case *ast.InfixExpression: - left := Eval(node.Left, ctx, env) + left := Eval(ctx, node.Left, env) if isError(left) { return left } - right := Eval(node.Right, ctx, env) + right := Eval(ctx, node.Right, env) if isError(right) { return right } return evalInfixExpression(node.Operator, left, right) case *ast.CallExpression: - function := Eval(node.Function, ctx, env) + function := Eval(ctx, node.Function, env) if isError(function) { return function } - args := evalExpressions(node.Arguments, ctx, env) + args := evalExpressions(ctx, node.Arguments, env) if len(args) == 1 && isError(args[0]) { return args[0] } @@ -173,39 +173,39 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob return object.String{Value: node.Value} case *ast.ArrayLiteral: - elements := evalExpressions(node.Elements, ctx, env) + elements := evalExpressions(ctx, node.Elements, env) if len(elements) == 1 && isError(elements[0]) { return elements[0] } return &object.Array{Elements: elements} case *ast.IndexExpression: - left := Eval(node.Left, ctx, env) + left := Eval(ctx, node.Left, env) if isError(left) { return left } - index := Eval(node.Index, ctx, env) + index := Eval(ctx, node.Index, env) if isError(index) { return index } return evalIndexExpression(left, index) case *ast.HashLiteral: - return evalHashLiteral(node, ctx, env) + return evalHashLiteral(ctx, node, env) } return nil } -func evalImportExpression(ie *ast.ImportExpression, ctx context.Context, env *object.Environment) object.Object { - name := Eval(ie.Name, ctx, env) +func evalImportExpression(ctx context.Context, ie *ast.ImportExpression, env *object.Environment) object.Object { + name := Eval(ctx, ie.Name, env) if isError(name) { return name } if s, ok := name.(object.String); ok { - attrs := EvalModule(ctx, s.Value) + attrs := Module(ctx, s.Value) if isError(attrs) { return attrs } @@ -214,17 +214,17 @@ func evalImportExpression(ie *ast.ImportExpression, ctx context.Context, env *ob return newError("ImportError: invalid import path '%s'", name) } -func evalWhileExpression(we *ast.WhileExpression, ctx context.Context, env *object.Environment) object.Object { +func evalWhileExpression(ctx context.Context, we *ast.WhileExpression, env *object.Environment) object.Object { var result object.Object for { - condition := Eval(we.Condition, ctx, env) + condition := Eval(ctx, we.Condition, env) if isError(condition) { return condition } if isTruthy(condition) { - result = Eval(we.Consequence, ctx, env) + result = Eval(ctx, we.Consequence, env) } else { break } @@ -237,11 +237,11 @@ func evalWhileExpression(we *ast.WhileExpression, ctx context.Context, env *obje return NULL } -func evalProgram(program *ast.Program, ctx context.Context, env *object.Environment) object.Object { +func evalProgram(ctx context.Context, program *ast.Program, env *object.Environment) object.Object { var result object.Object for _, statement := range program.Statements { - result = Eval(statement, ctx, env) + result = Eval(ctx, statement, env) switch result := result.(type) { case object.ReturnValue: @@ -255,11 +255,11 @@ func evalProgram(program *ast.Program, ctx context.Context, env *object.Environm return result } -func evalBlockStatements(block *ast.BlockStatement, ctx context.Context, env *object.Environment) object.Object { +func evalBlockStatements(ctx context.Context, block *ast.BlockStatement, env *object.Environment) object.Object { var result object.Object for _, statement := range block.Statements { - result = Eval(statement, ctx, env) + result = Eval(ctx, statement, env) if result != nil { rt := result.Type() @@ -385,17 +385,17 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje return object.String{Value: strings.Repeat(rightVal, int(leftVal))} case operator == "==": - return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 0) + return nativeBoolToBooleanObject(left.Compare(right) == 0) case operator == "!=": - return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) != 0) + return nativeBoolToBooleanObject(left.Compare(right) != 0) case operator == "<=": - return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) < 1) + return nativeBoolToBooleanObject(left.Compare(right) < 1) case operator == ">=": - return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) > -1) + return nativeBoolToBooleanObject(left.Compare(right) > -1) case operator == "<": - return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == -1) + return nativeBoolToBooleanObject(left.Compare(right) == -1) case operator == ">": - return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 1) + return nativeBoolToBooleanObject(left.Compare(right) == 1) case left.Type() == object.BooleanType && right.Type() == object.BooleanType: return evalBooleanInfixExpression(operator, left, right) @@ -477,16 +477,16 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje } } -func evalIfExpression(ie *ast.IfExpression, ctx context.Context, env *object.Environment) object.Object { - condition := Eval(ie.Condition, ctx, env) +func evalIfExpression(ctx context.Context, ie *ast.IfExpression, env *object.Environment) object.Object { + condition := Eval(ctx, ie.Condition, env) if isError(condition) { return condition } if isTruthy(condition) { - return Eval(ie.Consequence, ctx, env) + return Eval(ctx, ie.Consequence, env) } else if ie.Alternative != nil { - return Eval(ie.Alternative, ctx, env) + return Eval(ctx, ie.Alternative, env) } else { return NULL } @@ -509,8 +509,8 @@ func newError(format string, a ...interface{}) object.Error { return object.Error{Message: fmt.Sprintf(format, a...)} } -// EvalModule evaluates the named module and returns a object.Module objec -func EvalModule(ctx context.Context, name string) object.Object { +// Module evaluates the named module and returns a object.Module object +func Module(ctx context.Context, name string) object.Object { filename := utils.FindModule(name) b, err := os.ReadFile(filename) @@ -527,7 +527,7 @@ func EvalModule(ctx context.Context, name string) object.Object { } env := object.NewEnvironment() - Eval(module, ctx, env) + Eval(ctx, module, env) return env.ExportedHash() } @@ -544,11 +544,11 @@ func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object return newError("identifier not found: " + node.Value) } -func evalExpressions(exps []ast.Expression, ctx context.Context, env *object.Environment) []object.Object { +func evalExpressions(ctx context.Context, exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object for _, e := range exps { - evaluated := Eval(e, ctx, env) + evaluated := Eval(ctx, e, env) if isError(evaluated) { return []object.Object{evaluated} } @@ -563,7 +563,7 @@ func applyFunction(ctx context.Context, fn object.Object, args []object.Object) case object.Function: extendedEnv := extendFunctionEnv(fn, args) - evaluated := Eval(fn.Body, ctx, extendedEnv) + evaluated := Eval(ctx, fn.Body, extendedEnv) return unwrapReturnValue(evaluated) case object.Builtin: @@ -656,11 +656,11 @@ func evalArrayIndexExpression(array, index object.Object) object.Object { return arrayObject.Elements[idx] } -func evalHashLiteral(node *ast.HashLiteral, ctx context.Context, env *object.Environment) object.Object { +func evalHashLiteral(ctx context.Context, node *ast.HashLiteral, env *object.Environment) object.Object { pairs := make(map[object.HashKey]object.HashPair) for keyNode, valueNode := range node.Pairs { - key := Eval(keyNode, ctx, env) + key := Eval(ctx, keyNode, env) if isError(key) { return key } @@ -670,7 +670,7 @@ func evalHashLiteral(node *ast.HashLiteral, ctx context.Context, env *object.Env return newError("unusable as hash key: %s", key.Type()) } - value := Eval(valueNode, ctx, env) + value := Eval(ctx, valueNode, env) if isError(value) { return value } diff --git a/internal/evaluator/evaluator_test.go b/internal/evaluator/evaluator_test.go index 980d78f..a77eeaf 100644 --- a/internal/evaluator/evaluator_test.go +++ b/internal/evaluator/evaluator_test.go @@ -800,7 +800,7 @@ func testEval(input string) object.Object { program := p.ParseProgram() env := object.NewEnvironment() - return Eval(program, context.New(), env) + return Eval(context.New(), program, env) } func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { diff --git a/internal/object/array.go b/internal/object/array.go index 9d7ad92..48ce477 100644 --- a/internal/object/array.go +++ b/internal/object/array.go @@ -63,11 +63,8 @@ func (ao *Array) String() string { return ao.Inspect() } -func (ao *Array) Less(i, j int) bool { - if cmp, ok := ao.Elements[i].(Comparable); ok { - return cmp.Compare(ao.Elements[j]) == -1 - } - return false +func (a *Array) Less(i, j int) bool { + return a.Elements[i].Compare(a.Elements[j]) == -1 } func (a *Array) Add(other Object) (Object, error) { @@ -129,18 +126,15 @@ func (ao *Array) Set(index, other Object) error { return nil } -func (ao *Array) Compare(other Object) int { +func (a *Array) Compare(other Object) int { if obj, ok := other.(*Array); ok { - if len(ao.Elements) != len(obj.Elements) { + if len(a.Elements) != len(obj.Elements) { return -1 } - for i, el := range ao.Elements { - cmp, ok := el.(Comparable) - if !ok { - return -1 - } - if cmp.Compare(obj.Elements[i]) != 0 { - return cmp.Compare(obj.Elements[i]) + for i, el := range a.Elements { + val := el.Compare(obj.Elements[i]) + if val != 0 { + return val } } diff --git a/internal/object/builtin.go b/internal/object/builtin.go index 6fdccc8..3bd7ba9 100644 --- a/internal/object/builtin.go +++ b/internal/object/builtin.go @@ -19,6 +19,19 @@ func (b Builtin) Inspect() string { return fmt.Sprintf("", b.Name) } +func (b Builtin) Compare(other Object) int { + if obj, ok := other.(Builtin); ok { + if b.Name > obj.Name { + return 1 + } + if b.Name == obj.Name { + return 0 + } + return -1 + } + return -1 +} + func (b Builtin) String() string { return b.Inspect() } diff --git a/internal/object/closure.go b/internal/object/closure.go index bf00f09..c568b42 100644 --- a/internal/object/closure.go +++ b/internal/object/closure.go @@ -23,11 +23,16 @@ func (cf *CompiledFunction) Inspect() string { return fmt.Sprintf("CompiledFunction[%p]", cf) } +func (cf CompiledFunction) Compare(other Object) int { + return -1 +} + func (cf *CompiledFunction) String() string { return cf.Inspect() } type Closure struct { + BaseObject Fn *CompiledFunction Free []Object } diff --git a/internal/object/error.go b/internal/object/error.go index 33d64c5..3810198 100644 --- a/internal/object/error.go +++ b/internal/object/error.go @@ -23,6 +23,23 @@ func (e Error) Copy() Object { return Error{Message: e.Message} } +func (e Error) Compare(other Object) int { + if obj, ok := other.(Error); ok { + if e.Message > obj.Message { + return 1 + } + if e.Message == obj.Message { + return 0 + } + return -1 + } + return -1 +} + +func (e Error) Error() string { + return e.Message +} + func (e Error) String() string { return e.Message } diff --git a/internal/object/function.go b/internal/object/function.go index b56c07f..969ae16 100644 --- a/internal/object/function.go +++ b/internal/object/function.go @@ -7,6 +7,8 @@ import ( ) type Function struct { + BaseObject + Parameters []*ast.Identifier Body *ast.BlockStatement Env *Environment @@ -43,6 +45,7 @@ func (f Function) String() string { } type ReturnValue struct { + BaseObject Value Object } diff --git a/internal/object/hash.go b/internal/object/hash.go index e0d24c4..cd79dd4 100644 --- a/internal/object/hash.go +++ b/internal/object/hash.go @@ -97,18 +97,14 @@ func (h *Hash) Compare(other Object) int { return -1 } for _, pair := range h.Pairs { - left := pair.Value - hashed := left.(Hasher) + hashed := pair.Value.(Hasher) right, ok := obj.Pairs[hashed.Hash()] if !ok { return -1 } - cmp, ok := left.(Comparable) - if !ok { - return -1 - } - if cmp.Compare(right.Value) != 0 { - return cmp.Compare(right.Value) + val := pair.Value.Compare(right.Value) + if val != 0 { + return val } } diff --git a/internal/object/object.go b/internal/object/object.go index 43d3097..b571648 100644 --- a/internal/object/object.go +++ b/internal/object/object.go @@ -57,11 +57,6 @@ func (t Type) String() string { } } -// Comparable is the interface for objects to implement suitable comparisons -type Comparable interface { - Compare(other Object) int -} - // Copyable is the interface for creating copies of objects type Copyable interface { Copy() Object @@ -73,9 +68,14 @@ type Object interface { fmt.Stringer Type() Type Bool() bool + Compare(Object) int Inspect() string } +type BaseObject struct{} + +func (o BaseObject) Compare(other Object) int { return -1 } + // Hasher is the interface for objects to provide suitable hash keys type Hasher interface { Hash() HashKey diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 07afab0..da490de 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -1,6 +1,8 @@ package server import ( + "fmt" + "io" "monkey" "net/http" @@ -12,6 +14,9 @@ func (s *Server) runHandler() router.Handle { opts := &monkey.Options{ Stdout: w, Stderr: w, + Exit: func(status int) { + io.WriteString(w, fmt.Sprintf("

Program exited with %d.

", status)) + }, } err := monkey.ExecString(r.FormValue("code"), opts) diff --git a/internal/server/static/index.html b/internal/server/static/index.html index 2e50045..9cf0594 100644 --- a/internal/server/static/index.html +++ b/internal/server/static/index.html @@ -9,7 +9,7 @@
- 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)