diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml deleted file mode 100644 index 6c08a85..0000000 --- a/.gitea/workflows/publish.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- - -name: Publish Image - -on: - push: - branches: [master] - -env: - REGISTRY: r.unflavoredmeson.com - IMAGE: prologic/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/.gitignore b/.gitignore index dc22dd0..bf6d12a 100644 --- a/.gitignore +++ b/.gitignore @@ -93,5 +93,6 @@ fabric.properties /dist /.vscode /monkey +/monkey-server /examples/fib vendor \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index d7f4aef..4dfb99f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -6,7 +6,7 @@ builds: flags: -tags "static_build" ldflags: >- -w - -X go.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}} + -X gitea.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}} env: - CGO_ENABLED=0 goos: @@ -25,7 +25,7 @@ builds: flags: -tags "static_build" ldflags: >- -w - -X go.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}} + -X gitea.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}} env: - CGO_ENABLED=0 goos: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index 5085e87..d710709 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CGO_ENABLED=0 -VERSION ?= $(shell git describe 2>/dev/null || echo "") +VERSION ?= $(shell git describe) DESTDIR ?= $(GOBIN) @@ -33,13 +33,13 @@ build: clean cli server ## Build monkey cli: ## Build monkey CLI @go build \ -tags "netgo static_build" -installsuffix netgo \ - -ldflags "-w -X go.unflavoredmeson.com/monkey/v2.MonkeyVersion=$(VERSION)" \ + -ldflags "-w -X go.unflavoredmeson.com/monkey/MonkeyVersion=$(VERSION)" \ ./cmd/monkey/... server: ## Build Monkey server @go build \ -tags "netgo static_build" -installsuffix netgo \ - -ldflags "-w -X go.unflavoredmeson.com/monkey/v2.MonkeyVersion=$(VERSION)" \ + -ldflags "-w -X go.unflavoredmeson.com/monkey/MonkeyVersion=$(VERSION)" \ ./cmd/monkey/... install: cli server ## Install monkey to $DESTDIR diff --git a/cmd/monkey-server/main.go b/cmd/monkey-server/main.go new file mode 100644 index 0000000..6f23335 --- /dev/null +++ b/cmd/monkey-server/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "flag" + "log" + "os" + "os/signal" +) + +var ( + bind string +) + +func init() { + flag.StringVar(&bind, "b", ":8000", "[interface]: to bind to") +} + +func main() { + flag.Parse() + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + + svr, err := server.NewServer(server.WithBind(bind)) + if err != nil { + log.Fatal(err) + } + + log.Fatal(svr.Run(ctx)) +} diff --git a/cmd/monkey/main.go b/cmd/monkey/main.go index 84902b5..d56a882 100644 --- a/cmd/monkey/main.go +++ b/cmd/monkey/main.go @@ -23,20 +23,26 @@ func main() { flag.BoolVar(&trace, "T", false, "Enable VM tracing") flag.Parse() + opts := &monkey.Options{ + Debug: debug, + Trace: trace, + } + switch { case compile: - monkey.CompileFiles(flag.Args(), debug) + monkey.CompileFiles(flag.Args(), opts) case version: monkey.PrintVersionInfo(os.Stdout) case flag.NArg() > 0: - monkey.ExecFile(flag.Arg(0), flag.Args()[1:], debug, trace) + opts.Args = flag.Args()[1:] + monkey.ExecFile(flag.Arg(0), opts) case simple: - monkey.SimpleREPL(flag.Args(), debug, trace) + monkey.SimpleREPL(flag.Args(), opts) default: - monkey.REPL(flag.Args(), debug, trace) + monkey.REPL(flag.Args(), opts) } } diff --git a/go.mod b/go.mod index 62cf442..f9138f3 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,17 @@ module monkey go 1.21 require ( + github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 github.com/tebeka/atexit v0.3.0 + go.mills.io/logger v0.0.0-20230806012737-485dbd691907 + go.mills.io/router v0.0.0-20230812054754-cb20d161aabe golang.org/x/term v0.18.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 8ef982f..1fc6210 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,24 @@ 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/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= +go.mills.io/logger v0.0.0-20230806012737-485dbd691907 h1:KXwGupN4n3h/t9HyTLykODg1ope7KtXaknALkBMaaz4= +go.mills.io/logger v0.0.0-20230806012737-485dbd691907/go.mod h1:A+23JY9iOHzujHnRYbFKVzLLAQVObxHnsap8kjAjuQ8= +go.mills.io/router v0.0.0-20230812054754-cb20d161aabe h1:kETYyjvgukkdNkTWpWilvUb2NGoiXFZOjg4Qa9L2iKA= +go.mills.io/router v0.0.0-20230812054754-cb20d161aabe/go.mod h1:Cz4Yur0PWgwzE6udB/ydRY9X+vTzpgVGfdmkJjQ3XkM= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= @@ -16,5 +26,6 @@ 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/abs.go b/internal/builtins/abs.go index fc0ab7f..5683341 100644 --- a/internal/builtins/abs.go +++ b/internal/builtins/abs.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Abs ... -func Abs(args ...object.Object) object.Object { +func Abs(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "abs", args, typing.ExactArgs(1), diff --git a/internal/builtins/accept.go b/internal/builtins/accept.go index ea9f906..805d471 100644 --- a/internal/builtins/accept.go +++ b/internal/builtins/accept.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" ) // Accept ... -func Accept(args ...object.Object) object.Object { +func Accept(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "accept", args, typing.ExactArgs(1), diff --git a/internal/builtins/args.go b/internal/builtins/args.go index 65d6c4a..b5dcc87 100644 --- a/internal/builtins/args.go +++ b/internal/builtins/args.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Args ... -func args(args ...object.Object) object.Object { +func args(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "args", args, typing.ExactArgs(0), @@ -14,8 +15,8 @@ func args(args ...object.Object) object.Object { return newError(err.Error()) } - elements := make([]object.Object, len(object.Args)) - for i, arg := range object.Args { + elements := make([]object.Object, len(ctx.Args())) + for i, arg := range ctx.Args() { elements[i] = object.String{Value: arg} } return &object.Array{Elements: elements} diff --git a/internal/builtins/assert.go b/internal/builtins/assert.go index b11b7fa..21b41f8 100644 --- a/internal/builtins/assert.go +++ b/internal/builtins/assert.go @@ -2,13 +2,14 @@ package builtins import ( "fmt" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "os" ) // Assert ... -func Assert(args ...object.Object) object.Object { +func Assert(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "assert", args, typing.ExactArgs(2), diff --git a/internal/builtins/bin.go b/internal/builtins/bin.go index 1357773..71584e0 100644 --- a/internal/builtins/bin.go +++ b/internal/builtins/bin.go @@ -2,13 +2,14 @@ package builtins import ( "fmt" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strconv" ) // Bin ... -func Bin(args ...object.Object) object.Object { +func Bin(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "bin", args, typing.ExactArgs(1), diff --git a/internal/builtins/bind.go b/internal/builtins/bind.go index 641673b..5513074 100644 --- a/internal/builtins/bind.go +++ b/internal/builtins/bind.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" ) // Bind ... -func Bind(args ...object.Object) object.Object { +func Bind(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "bind", args, typing.ExactArgs(2), diff --git a/internal/builtins/bool.go b/internal/builtins/bool.go index 13f8136..55bb9ed 100644 --- a/internal/builtins/bool.go +++ b/internal/builtins/bool.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Bool ... -func Bool(args ...object.Object) object.Object { +func Bool(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "bool", args, typing.ExactArgs(1), diff --git a/internal/builtins/chr.go b/internal/builtins/chr.go index f8cbb69..7cbe582 100644 --- a/internal/builtins/chr.go +++ b/internal/builtins/chr.go @@ -2,12 +2,13 @@ package builtins import ( "fmt" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Chr ... -func Chr(args ...object.Object) object.Object { +func Chr(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "chr", args, typing.ExactArgs(1), diff --git a/internal/builtins/close.go b/internal/builtins/close.go index eb303c0..10a4a70 100644 --- a/internal/builtins/close.go +++ b/internal/builtins/close.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" ) // Close ... -func Close(args ...object.Object) object.Object { +func Close(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "close", args, typing.ExactArgs(1), diff --git a/internal/builtins/connect.go b/internal/builtins/connect.go index c5e2eee..10ece30 100644 --- a/internal/builtins/connect.go +++ b/internal/builtins/connect.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" ) // Connect ... -func Connect(args ...object.Object) object.Object { +func Connect(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "connect", args, typing.ExactArgs(2), diff --git a/internal/builtins/divmod.go b/internal/builtins/divmod.go index cd21a24..642f47e 100644 --- a/internal/builtins/divmod.go +++ b/internal/builtins/divmod.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Divmod ... -func Divmod(args ...object.Object) object.Object { +func Divmod(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "divmod", args, typing.ExactArgs(2), diff --git a/internal/builtins/exit.go b/internal/builtins/exit.go index f440507..ae729e8 100644 --- a/internal/builtins/exit.go +++ b/internal/builtins/exit.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Exit ... -func Exit(args ...object.Object) object.Object { +func Exit(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "exit", args, typing.RangeOfArgs(0, 1), @@ -20,7 +21,7 @@ func Exit(args ...object.Object) object.Object { status = int(args[0].(object.Integer).Value) } - object.ExitFunction(status) + ctx.Exit(status) return nil } diff --git a/internal/builtins/ffi.go b/internal/builtins/ffi.go index 1f5d158..66d15ea 100644 --- a/internal/builtins/ffi.go +++ b/internal/builtins/ffi.go @@ -1,6 +1,7 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) @@ -11,7 +12,7 @@ import ( ) // FFI ... -func FFI(args ...object.Object) object.Object { +func FFI(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "ffi", args, typing.ExactArgs(2), diff --git a/internal/builtins/find.go b/internal/builtins/find.go index 49057b7..4b6c6c8 100644 --- a/internal/builtins/find.go +++ b/internal/builtins/find.go @@ -1,6 +1,7 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "sort" @@ -11,7 +12,7 @@ import ( ) // Find ... -func Find(args ...object.Object) object.Object { +func Find(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "find", args, typing.ExactArgs(2), diff --git a/internal/builtins/first.go b/internal/builtins/first.go index 3a2604c..a2bc8c7 100644 --- a/internal/builtins/first.go +++ b/internal/builtins/first.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // First ... -func First(args ...object.Object) object.Object { +func First(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "first", args, typing.ExactArgs(1), diff --git a/internal/builtins/hash.go b/internal/builtins/hash.go index eb84d6a..2bd62bb 100644 --- a/internal/builtins/hash.go +++ b/internal/builtins/hash.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // HashOf ... -func HashOf(args ...object.Object) object.Object { +func HashOf(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "hash", args, typing.ExactArgs(1), diff --git a/internal/builtins/hex.go b/internal/builtins/hex.go index 7c1c3e6..8e76205 100644 --- a/internal/builtins/hex.go +++ b/internal/builtins/hex.go @@ -2,13 +2,14 @@ package builtins import ( "fmt" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strconv" ) // Hex ... -func Hex(args ...object.Object) object.Object { +func Hex(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "hex", args, typing.ExactArgs(1), diff --git a/internal/builtins/id.go b/internal/builtins/id.go index e479719..3226052 100644 --- a/internal/builtins/id.go +++ b/internal/builtins/id.go @@ -2,12 +2,13 @@ package builtins import ( "fmt" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // IdOf ... -func IdOf(args ...object.Object) object.Object { +func IdOf(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "id", args, typing.ExactArgs(1), diff --git a/internal/builtins/input.go b/internal/builtins/input.go index 82ff13b..ec55b27 100644 --- a/internal/builtins/input.go +++ b/internal/builtins/input.go @@ -4,13 +4,14 @@ import ( "bufio" "fmt" "io" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "os" ) // Input ... -func Input(args ...object.Object) object.Object { +func Input(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "input", args, typing.RangeOfArgs(0, 1), diff --git a/internal/builtins/int.go b/internal/builtins/int.go index a9179cc..c63b4ac 100644 --- a/internal/builtins/int.go +++ b/internal/builtins/int.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strconv" ) // Int ... -func Int(args ...object.Object) object.Object { +func Int(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "int", args, typing.ExactArgs(1), diff --git a/internal/builtins/join.go b/internal/builtins/join.go index eabf90d..0c1d8f2 100644 --- a/internal/builtins/join.go +++ b/internal/builtins/join.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strings" ) // Join ... -func Join(args ...object.Object) object.Object { +func Join(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "join", args, typing.ExactArgs(2), diff --git a/internal/builtins/last.go b/internal/builtins/last.go index 3b0f314..3c383d9 100644 --- a/internal/builtins/last.go +++ b/internal/builtins/last.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Last ... -func Last(args ...object.Object) object.Object { +func Last(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "last", args, typing.ExactArgs(1), diff --git a/internal/builtins/len.go b/internal/builtins/len.go index 89a4215..80e14f7 100644 --- a/internal/builtins/len.go +++ b/internal/builtins/len.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Len ... -func Len(args ...object.Object) object.Object { +func Len(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "len", args, typing.ExactArgs(1), diff --git a/internal/builtins/listen.go b/internal/builtins/listen.go index 206b7bb..8cfe202 100644 --- a/internal/builtins/listen.go +++ b/internal/builtins/listen.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" ) // Listen ... -func Listen(args ...object.Object) object.Object { +func Listen(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "listen", args, typing.ExactArgs(2), diff --git a/internal/builtins/lower.go b/internal/builtins/lower.go index 9362b8a..12a9f0e 100644 --- a/internal/builtins/lower.go +++ b/internal/builtins/lower.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strings" ) // Lower ... -func Lower(args ...object.Object) object.Object { +func Lower(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "lower", args, typing.ExactArgs(1), diff --git a/internal/builtins/max.go b/internal/builtins/max.go index 0dac2f0..1308ccb 100644 --- a/internal/builtins/max.go +++ b/internal/builtins/max.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "sort" ) // Max ... -func Max(args ...object.Object) object.Object { +func Max(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "max", args, typing.ExactArgs(1), diff --git a/internal/builtins/min.go b/internal/builtins/min.go index 4f4a279..8e43331 100644 --- a/internal/builtins/min.go +++ b/internal/builtins/min.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "sort" ) // Min ... -func Min(args ...object.Object) object.Object { +func Min(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "min", args, typing.ExactArgs(1), diff --git a/internal/builtins/oct.go b/internal/builtins/oct.go index 2e82f74..487d347 100644 --- a/internal/builtins/oct.go +++ b/internal/builtins/oct.go @@ -2,13 +2,14 @@ package builtins import ( "fmt" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strconv" ) // Oct ... -func Oct(args ...object.Object) object.Object { +func Oct(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "oct", args, typing.ExactArgs(1), diff --git a/internal/builtins/open.go b/internal/builtins/open.go index 933bd3b..fd0f944 100644 --- a/internal/builtins/open.go +++ b/internal/builtins/open.go @@ -3,6 +3,7 @@ package builtins import ( "fmt" "log" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "os" @@ -51,7 +52,7 @@ func parseMode(mode string) (int, error) { } // Open ... -func Open(args ...object.Object) object.Object { +func Open(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "open", args, typing.RangeOfArgs(1, 2), diff --git a/internal/builtins/ord.go b/internal/builtins/ord.go index 1497523..2089d9a 100644 --- a/internal/builtins/ord.go +++ b/internal/builtins/ord.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Ord ... -func Ord(args ...object.Object) object.Object { +func Ord(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "ord", args, typing.ExactArgs(1), diff --git a/internal/builtins/pop.go b/internal/builtins/pop.go index af98a21..9615373 100644 --- a/internal/builtins/pop.go +++ b/internal/builtins/pop.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Pop ... -func Pop(args ...object.Object) object.Object { +func Pop(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "pop", args, typing.ExactArgs(1), diff --git a/internal/builtins/pow.go b/internal/builtins/pow.go index cf7d859..6025c84 100644 --- a/internal/builtins/pow.go +++ b/internal/builtins/pow.go @@ -1,6 +1,7 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) @@ -18,7 +19,7 @@ func pow(x, y int64) int64 { } // Pow ... -func Pow(args ...object.Object) object.Object { +func Pow(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "pow", args, typing.ExactArgs(2), diff --git a/internal/builtins/print.go b/internal/builtins/print.go index 49cc517..36f9f6f 100644 --- a/internal/builtins/print.go +++ b/internal/builtins/print.go @@ -2,12 +2,13 @@ package builtins import ( "fmt" + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Print ... -func Print(args ...object.Object) object.Object { +func Print(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "print", args, typing.MinimumArgs(1), @@ -16,13 +17,13 @@ func Print(args ...object.Object) object.Object { return newError(err.Error()) } - fmt.Fprint(object.Stdout, args[0].String()) + fmt.Fprint(ctx.Stdout(), args[0].String()) return nil } // Println ... -func Println(args ...object.Object) object.Object { +func Println(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "println", args, typing.MinimumArgs(1), @@ -30,7 +31,7 @@ func Println(args ...object.Object) object.Object { return newError(err.Error()) } - fmt.Fprintln(object.Stdout, args[0].String()) + fmt.Fprintln(ctx.Stdout(), args[0].String()) return nil } diff --git a/internal/builtins/push.go b/internal/builtins/push.go index b0c781b..f43f0c2 100644 --- a/internal/builtins/push.go +++ b/internal/builtins/push.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Push ... -func Push(args ...object.Object) object.Object { +func Push(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "push", args, typing.ExactArgs(2), diff --git a/internal/builtins/read.go b/internal/builtins/read.go index d11ec16..15af4d2 100644 --- a/internal/builtins/read.go +++ b/internal/builtins/read.go @@ -1,6 +1,7 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" @@ -10,7 +11,7 @@ import ( const DefaultBufferSize = 4096 // Read ... -func Read(args ...object.Object) object.Object { +func Read(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "read", args, typing.RangeOfArgs(1, 2), diff --git a/internal/builtins/readfile.go b/internal/builtins/readfile.go index c127de1..2aeb0a2 100644 --- a/internal/builtins/readfile.go +++ b/internal/builtins/readfile.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "os" ) // ReadFile ... -func ReadFile(args ...object.Object) object.Object { +func ReadFile(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "readfile", args, typing.ExactArgs(1), diff --git a/internal/builtins/rest.go b/internal/builtins/rest.go index 74e9c57..924cc27 100644 --- a/internal/builtins/rest.go +++ b/internal/builtins/rest.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Rest ... -func Rest(args ...object.Object) object.Object { +func Rest(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "rest", args, typing.ExactArgs(1), diff --git a/internal/builtins/reversed.go b/internal/builtins/reversed.go index 4b86cd8..e07330e 100644 --- a/internal/builtins/reversed.go +++ b/internal/builtins/reversed.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Reversed ... -func Reversed(args ...object.Object) object.Object { +func Reversed(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "reversed", args, typing.ExactArgs(1), diff --git a/internal/builtins/seek.go b/internal/builtins/seek.go index 8fd27a3..a91b5a3 100644 --- a/internal/builtins/seek.go +++ b/internal/builtins/seek.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" ) // Seek ... -func Seek(args ...object.Object) object.Object { +func Seek(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "seek", args, typing.RangeOfArgs(1, 3), diff --git a/internal/builtins/socket.go b/internal/builtins/socket.go index fb21399..7517b8e 100644 --- a/internal/builtins/socket.go +++ b/internal/builtins/socket.go @@ -1,6 +1,7 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strings" @@ -8,7 +9,7 @@ import ( ) // Socket ... -func Socket(args ...object.Object) object.Object { +func Socket(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "socket", args, typing.ExactArgs(1), diff --git a/internal/builtins/sorted.go b/internal/builtins/sorted.go index 466e135..d51662e 100644 --- a/internal/builtins/sorted.go +++ b/internal/builtins/sorted.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "sort" ) // Sorted ... -func Sorted(args ...object.Object) object.Object { +func Sorted(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "sort", args, typing.ExactArgs(1), diff --git a/internal/builtins/split.go b/internal/builtins/split.go index 989fab7..51ba771 100644 --- a/internal/builtins/split.go +++ b/internal/builtins/split.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strings" ) // Split ... -func Split(args ...object.Object) object.Object { +func Split(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "split", args, typing.RangeOfArgs(1, 2), diff --git a/internal/builtins/str.go b/internal/builtins/str.go index b45447c..085d4ba 100644 --- a/internal/builtins/str.go +++ b/internal/builtins/str.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // Str ... -func Str(args ...object.Object) object.Object { +func Str(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "str", args, typing.ExactArgs(1), diff --git a/internal/builtins/typeof.go b/internal/builtins/typeof.go index 95b5ef1..ac8746d 100644 --- a/internal/builtins/typeof.go +++ b/internal/builtins/typeof.go @@ -1,12 +1,13 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" ) // TypeOf ... -func TypeOf(args ...object.Object) object.Object { +func TypeOf(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "type", args, typing.ExactArgs(1), diff --git a/internal/builtins/upper.go b/internal/builtins/upper.go index c8702ea..368f41e 100644 --- a/internal/builtins/upper.go +++ b/internal/builtins/upper.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "strings" ) // Upper ... -func Upper(args ...object.Object) object.Object { +func Upper(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "upper", args, typing.ExactArgs(1), diff --git a/internal/builtins/write.go b/internal/builtins/write.go index 6b801f4..b851d45 100644 --- a/internal/builtins/write.go +++ b/internal/builtins/write.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "syscall" ) // Write ... -func Write(args ...object.Object) object.Object { +func Write(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "write", args, typing.ExactArgs(2), diff --git a/internal/builtins/writefile.go b/internal/builtins/writefile.go index 8339dfa..2728850 100644 --- a/internal/builtins/writefile.go +++ b/internal/builtins/writefile.go @@ -1,13 +1,14 @@ package builtins import ( + "monkey/internal/context" "monkey/internal/object" "monkey/internal/typing" "os" ) // WriteFile ... -func WriteFile(args ...object.Object) object.Object { +func WriteFile(ctx context.Context, args ...object.Object) object.Object { if err := typing.Check( "writefile", args, typing.ExactArgs(2), diff --git a/internal/context/context.go b/internal/context/context.go new file mode 100644 index 0000000..b9ca65d --- /dev/null +++ b/internal/context/context.go @@ -0,0 +1,69 @@ +package context + +import ( + "io" + "os" +) + +// Context interface for handling command-line arguments and standard input/output streams +type Context interface { + Args() []string // Get the list of arguments passed to the program + Stdin() io.Reader // Get the standard input stream + Stdout() io.Writer // Get the standard output stream + Stderr() io.Writer // Get the error output stream + Exit(int) // Exit the program with a specific status code +} + +type context struct { + args []string + stdin io.Reader + stdout io.Writer + stderr io.Writer + exit func(int) +} + +func (ctx context) Args() []string { return ctx.args } +func (ctx context) Stdin() io.Reader { return ctx.stdin } +func (ctx context) Stdout() io.Writer { return ctx.stdout } +func (ctx context) Stderr() io.Writer { return ctx.stderr } +func (ctx context) Exit(status int) { ctx.exit(status) } + +type Option func(ctx *context) + +func WithArgs(args []string) Option { + return func(ctx *context) { ctx.args = args } +} + +func WithStdin(stdin io.Reader) Option { + return func(ctx *context) { ctx.stdin = stdin } +} + +func WithStdout(stdout io.Writer) Option { + return func(ctx *context) { ctx.stdout = stdout } +} + +func WithStderr(stderr io.Writer) Option { + return func(ctx *context) { ctx.stderr = stderr } +} + +func WithExit(exit func(int)) Option { + return func(ctx *context) { ctx.exit = exit } +} + +// New returns a new instance of the Context interface with +// the standard input, output, and error streams. +func New(opts ...Option) Context { + ctx := &context{ + args: os.Args[1:], // Skip monkey binary itself + stdin: os.Stdin, + stdout: os.Stdout, + stderr: os.Stderr, + exit: os.Exit, + } + + for _, opt := range opts { + opt(ctx) + } + + return ctx +} diff --git a/internal/evaluator/evaluator.go b/internal/evaluator/evaluator.go index 1e156e1..7db93a4 100644 --- a/internal/evaluator/evaluator.go +++ b/internal/evaluator/evaluator.go @@ -4,6 +4,7 @@ import ( "fmt" "monkey/internal/ast" "monkey/internal/builtins" + "monkey/internal/context" "monkey/internal/lexer" "monkey/internal/object" "monkey/internal/parser" @@ -25,37 +26,37 @@ func isError(obj object.Object) bool { return false } -func Eval(node ast.Node, env *object.Environment) object.Object { +func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Object { switch node := node.(type) { // Statements case *ast.Program: - return evalProgram(node, env) + return evalProgram(node, ctx, env) case *ast.ExpressionStatement: - return Eval(node.Expression, env) + return Eval(node.Expression, ctx, env) case *ast.BlockStatement: - return evalBlockStatements(node, env) + return evalBlockStatements(node, ctx, env) case *ast.IfExpression: - return evalIfExpression(node, env) + return evalIfExpression(node, ctx, env) case *ast.WhileExpression: - return evalWhileExpression(node, env) + return evalWhileExpression(node, ctx, env) case *ast.ImportExpression: - return evalImportExpression(node, env) + return evalImportExpression(node, ctx, env) case *ast.ReturnStatement: - val := Eval(node.ReturnValue, env) + val := Eval(node.ReturnValue, ctx, env) if isError(val) { return val } return object.ReturnValue{Value: val} case *ast.BindExpression: - value := Eval(node.Value, env) + value := Eval(node.Value, ctx, env) if isError(value) { return value } @@ -72,12 +73,12 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return newError("expected identifier on left got=%T", node.Left) case *ast.AssignmentExpression: - left := Eval(node.Left, env) + left := Eval(node.Left, ctx, env) if isError(left) { return left } - value := Eval(node.Value, env) + value := Eval(node.Value, ctx, env) if isError(value) { return value } @@ -85,13 +86,13 @@ func Eval(node ast.Node, env *object.Environment) object.Object { 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, env) + obj := Eval(ie.Left, ctx, env) if isError(obj) { return obj } if array, ok := obj.(*object.Array); ok { - index := Eval(ie.Index, env) + index := Eval(ie.Index, ctx, env) if isError(index) { return index } @@ -101,7 +102,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return newError("cannot index array with %#v", index) } } else if hash, ok := obj.(*object.Hash); ok { - key := Eval(ie.Index, env) + key := Eval(ie.Index, ctx, env) if isError(key) { return key } @@ -139,72 +140,72 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return NULL case *ast.PrefixExpression: - right := Eval(node.Right, env) + right := Eval(node.Right, ctx, env) if isError(right) { return right } return evalPrefixExpression(node.Operator, right) case *ast.InfixExpression: - left := Eval(node.Left, env) + left := Eval(node.Left, ctx, env) if isError(left) { return left } - right := Eval(node.Right, env) + right := Eval(node.Right, ctx, env) if isError(right) { return right } return evalInfixExpression(node.Operator, left, right) case *ast.CallExpression: - function := Eval(node.Function, env) + function := Eval(node.Function, ctx, env) if isError(function) { return function } - args := evalExpressions(node.Arguments, env) + args := evalExpressions(node.Arguments, ctx, env) if len(args) == 1 && isError(args[0]) { return args[0] } - return applyFunction(function, args) + return applyFunction(ctx, function, args) case *ast.StringLiteral: return object.String{Value: node.Value} case *ast.ArrayLiteral: - elements := evalExpressions(node.Elements, env) + elements := evalExpressions(node.Elements, ctx, env) if len(elements) == 1 && isError(elements[0]) { return elements[0] } return &object.Array{Elements: elements} case *ast.IndexExpression: - left := Eval(node.Left, env) + left := Eval(node.Left, ctx, env) if isError(left) { return left } - index := Eval(node.Index, env) + index := Eval(node.Index, ctx, env) if isError(index) { return index } return evalIndexExpression(left, index) case *ast.HashLiteral: - return evalHashLiteral(node, env) + return evalHashLiteral(node, ctx, env) } return nil } -func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) object.Object { - name := Eval(ie.Name, env) +func evalImportExpression(ie *ast.ImportExpression, ctx context.Context, env *object.Environment) object.Object { + name := Eval(ie.Name, ctx, env) if isError(name) { return name } if s, ok := name.(object.String); ok { - attrs := EvalModule(s.Value) + attrs := EvalModule(ctx, s.Value) if isError(attrs) { return attrs } @@ -213,17 +214,17 @@ func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) obj return newError("ImportError: invalid import path '%s'", name) } -func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { +func evalWhileExpression(we *ast.WhileExpression, ctx context.Context, env *object.Environment) object.Object { var result object.Object for { - condition := Eval(we.Condition, env) + condition := Eval(we.Condition, ctx, env) if isError(condition) { return condition } if isTruthy(condition) { - result = Eval(we.Consequence, env) + result = Eval(we.Consequence, ctx, env) } else { break } @@ -236,11 +237,11 @@ func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) objec return NULL } -func evalProgram(program *ast.Program, env *object.Environment) object.Object { +func evalProgram(program *ast.Program, ctx context.Context, env *object.Environment) object.Object { var result object.Object for _, statement := range program.Statements { - result = Eval(statement, env) + result = Eval(statement, ctx, env) switch result := result.(type) { case object.ReturnValue: @@ -254,11 +255,11 @@ func evalProgram(program *ast.Program, env *object.Environment) object.Object { return result } -func evalBlockStatements(block *ast.BlockStatement, env *object.Environment) object.Object { +func evalBlockStatements(block *ast.BlockStatement, ctx context.Context, env *object.Environment) object.Object { var result object.Object for _, statement := range block.Statements { - result = Eval(statement, env) + result = Eval(statement, ctx, env) if result != nil { rt := result.Type() @@ -476,16 +477,16 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje } } -func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { - condition := Eval(ie.Condition, env) +func evalIfExpression(ie *ast.IfExpression, ctx context.Context, env *object.Environment) object.Object { + condition := Eval(ie.Condition, ctx, env) if isError(condition) { return condition } if isTruthy(condition) { - return Eval(ie.Consequence, env) + return Eval(ie.Consequence, ctx, env) } else if ie.Alternative != nil { - return Eval(ie.Alternative, env) + return Eval(ie.Alternative, ctx, env) } else { return NULL } @@ -509,7 +510,7 @@ func newError(format string, a ...interface{}) object.Error { } // EvalModule evaluates the named module and returns a object.Module objec -func EvalModule(name string) object.Object { +func EvalModule(ctx context.Context, name string) object.Object { filename := utils.FindModule(name) b, err := os.ReadFile(filename) @@ -526,7 +527,7 @@ func EvalModule(name string) object.Object { } env := object.NewEnvironment() - Eval(module, env) + Eval(module, ctx, env) return env.ExportedHash() } @@ -543,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, env *object.Environment) []object.Object { +func evalExpressions(exps []ast.Expression, ctx context.Context, env *object.Environment) []object.Object { var result []object.Object for _, e := range exps { - evaluated := Eval(e, env) + evaluated := Eval(e, ctx, env) if isError(evaluated) { return []object.Object{evaluated} } @@ -557,16 +558,16 @@ func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Ob return result } -func applyFunction(fn object.Object, args []object.Object) object.Object { +func applyFunction(ctx context.Context, fn object.Object, args []object.Object) object.Object { switch fn := fn.(type) { case object.Function: extendedEnv := extendFunctionEnv(fn, args) - evaluated := Eval(fn.Body, extendedEnv) + evaluated := Eval(fn.Body, ctx, extendedEnv) return unwrapReturnValue(evaluated) case object.Builtin: - if result := fn.Fn(args...); result != nil { + if result := fn.Fn(ctx, args...); result != nil { return result } return NULL @@ -655,11 +656,11 @@ func evalArrayIndexExpression(array, index object.Object) object.Object { return arrayObject.Elements[idx] } -func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Object { +func evalHashLiteral(node *ast.HashLiteral, ctx context.Context, env *object.Environment) object.Object { pairs := make(map[object.HashKey]object.HashPair) for keyNode, valueNode := range node.Pairs { - key := Eval(keyNode, env) + key := Eval(keyNode, ctx, env) if isError(key) { return key } @@ -669,7 +670,7 @@ func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Obje return newError("unusable as hash key: %s", key.Type()) } - value := Eval(valueNode, env) + value := Eval(valueNode, ctx, env) if isError(value) { return value } diff --git a/internal/evaluator/evaluator_test.go b/internal/evaluator/evaluator_test.go index 014f809..980d78f 100644 --- a/internal/evaluator/evaluator_test.go +++ b/internal/evaluator/evaluator_test.go @@ -3,6 +3,7 @@ package evaluator import ( "errors" "github.com/stretchr/testify/assert" + "monkey/internal/context" "monkey/internal/lexer" "monkey/internal/object" "monkey/internal/parser" @@ -799,7 +800,7 @@ func testEval(input string) object.Object { program := p.ParseProgram() env := object.NewEnvironment() - return Eval(program, env) + return Eval(program, context.New(), env) } func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { diff --git a/internal/object/object.go b/internal/object/object.go index 614f683..43d3097 100644 --- a/internal/object/object.go +++ b/internal/object/object.go @@ -1,6 +1,9 @@ package object -import "fmt" +import ( + "fmt" + "monkey/internal/context" +) // Type represents the type of an object type Type int @@ -79,7 +82,7 @@ type Hasher interface { } // BuiltinFunction represents the builtin function type -type BuiltinFunction func(args ...Object) Object +type BuiltinFunction func(ctx context.Context, args ...Object) Object func AssertTypes(obj Object, types ...Type) bool { for _, t := range types { diff --git a/internal/object/state.go b/internal/object/state.go deleted file mode 100644 index 0b18f7e..0000000 --- a/internal/object/state.go +++ /dev/null @@ -1,14 +0,0 @@ -package object - -import ( - "io" - "os" -) - -var ( - Args []string = os.Args[1:] // Skip the monkey binary itself - Stdin io.Reader = os.Stdin - Stdout io.Writer = os.Stdout - Stderr io.Writer = os.Stderr - ExitFunction func(int) = os.Exit -) diff --git a/internal/server/handlers.go b/internal/server/handlers.go new file mode 100644 index 0000000..07afab0 --- /dev/null +++ b/internal/server/handlers.go @@ -0,0 +1,29 @@ +package server + +import ( + "monkey" + "net/http" + + "go.mills.io/router" +) + +func (s *Server) runHandler() router.Handle { + return func(w http.ResponseWriter, r *http.Request, p router.Params) { + opts := &monkey.Options{ + Stdout: w, + Stderr: w, + } + + err := monkey.ExecString(r.FormValue("code"), opts) + if err != nil { + http.Error(w, err.Error(), 400) + return + } + } +} + +func (s *Server) formatHandler() router.Handle { + return func(w http.ResponseWriter, r *http.Request, p router.Params) { + http.Error(w, "Not Implemented", http.StatusNotImplemented) + } +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..b51b948 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,110 @@ +// Package server implements the Monkey Lang Web Server that implements +// a web-based Playground for testing out and trying Monkey on the Web. +package server + +import ( + "context" + "embed" + "errors" + "fmt" + "io/fs" + "net/http" + "time" + + log "github.com/sirupsen/logrus" + "go.mills.io/logger" + "go.mills.io/router" +) + +const ( + // DefaultBind is the default [
]: to bind to + DefaultBind = ":8000" +) + +//go:embed static/* +var staticFS embed.FS + +// Option is a function type that configures the server +type Option func(svr *Server) error + +// WithBind configures the server with a bind interface and port in the form of [
]: +// For example: WithBind(":8000") +func WithBind(bind string) Option { + return func(svr *Server) error { + svr.bind = bind + return nil + } +} + +type Server struct { + bind string + routes *router.Router +} + +func NewServer(opts ...Option) (*Server, error) { + routes := router.New() + routes.Use(router.Middleware(func(next router.Handle) router.Handle { + return func(w http.ResponseWriter, r *http.Request, p router.Params) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "*") + next(w, r, p) + } + })) + + svr := &Server{ + bind: DefaultBind, + routes: routes, + } + + for _, opt := range opts { + if err := opt(svr); err != nil { + return nil, fmt.Errorf("error configuring server: %w", err) + } + } + + svr.initRoutes() + + return svr, nil +} + +func (s *Server) initRoutes() { + fs, err := fs.Sub(staticFS, "static") + if err != nil { + log.Fatal(err) + } + s.routes.ServeFiles("/*filepath", http.FS(fs)) + + s.routes.POST("/api/run", s.runHandler()) + s.routes.POST("/api/format", s.formatHandler()) +} + +// Run runs the server with the provided context and shuts down when the context is cancelled +func (s *Server) Run(ctx context.Context) error { + svr := &http.Server{ + Addr: s.bind, + Handler: logger.New(logger.Options{ + Prefix: "monkey", + RemoteAddressHeaders: []string{"X-Forwarded-For"}, + }).Handler(s.routes), + } + + go func() { + if err := svr.ListenAndServe(); err != nil { + if !errors.Is(err, http.ErrServerClosed) { + log.WithError(err).Error("svr.ListenAndServe error") + } + } + }() + + select { + case <-ctx.Done(): + ctx, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() + + if err := svr.Shutdown(ctx); err != nil { + return fmt.Errorf("error shutting down server: %w", err) + } + + return nil + } +} diff --git a/internal/server/static/ace/ace.js b/internal/server/static/ace/ace.js new file mode 100644 index 0000000..68b5ad5 --- /dev/null +++ b/internal/server/static/ace/ace.js @@ -0,0 +1,4 @@ +ace.define("ace/snippets/golang", ["require", "exports", "module"], function (e, t, n) { + "use strict"; + t.snippetText = undefined, t.scope = "golang" +}) \ No newline at end of file diff --git a/internal/server/static/ace/mode-golang.js b/internal/server/static/ace/mode-golang.js new file mode 100644 index 0000000..883e837 --- /dev/null +++ b/internal/server/static/ace/mode-golang.js @@ -0,0 +1,345 @@ +ace.define("ace/mode/doc_comment_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (e, t, n) { + "use strict"; + var r = e("../lib/oop"), i = e("./text_highlight_rules").TextHighlightRules, s = function () { + this.$rules = { + start: [{ + token: "comment.doc.tag", + regex: "@[\\w\\d_]+" + }, s.getTagRule(), {defaultToken: "comment.doc", caseInsensitive: !0}] + } + }; + r.inherits(s, i), s.getTagRule = function (e) { + return {token: "comment.doc.tag.storage.type", regex: "\\b(?:TODO|FIXME|XXX|HACK)\\b"} + }, s.getStartRule = function (e) { + return {token: "comment.doc", regex: "\\/\\*(?=\\*)", next: e} + }, s.getEndRule = function (e) { + return {token: "comment.doc", regex: "\\*\\/", next: e} + }, t.DocCommentHighlightRules = s +}), ace.define("ace/mode/golang_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/doc_comment_highlight_rules", "ace/mode/text_highlight_rules"], function (e, t, n) { + var r = e("../lib/oop"), i = e("./doc_comment_highlight_rules").DocCommentHighlightRules, + s = e("./text_highlight_rules").TextHighlightRules, o = function () { + var e = "else|break|case|return|goto|if|const|select|continue|struct|default|switch|for|range|func|import|package|chan|defer|fallthrough|go|interface|map|range|select|type|var", + t = "string|uint8|uint16|uint32|uint64|int8|int16|int32|int64|float32|float64|complex64|complex128|byte|rune|uint|int|uintptr|bool|error", + n = "new|close|cap|copy|panic|panicln|print|println|len|make|delete|real|recover|imag|append", + r = "nil|true|false|iota", s = this.createKeywordMapper({ + keyword: e, + "constant.language": r, + "support.function": n, + "support.type": t + }, ""), o = "\\\\(?:[0-7]{3}|x\\h{2}|u{4}|U\\h{6}|[abfnrtv'\"\\\\])".replace(/\\h/g, "[a-fA-F\\d]"); + this.$rules = { + start: [{token: "comment", regex: "\\/\\/.*$"}, i.getStartRule("doc-start"), { + token: "comment.start", + regex: "\\/\\*", + next: "comment" + }, {token: "string", regex: /"(?:[^"\\]|\\.)*?"/}, { + token: "string", + regex: "`", + next: "bqstring" + }, { + token: "constant.numeric", + regex: "'(?:[^\\'\ud800-\udbff]|[\ud800-\udbff][\udc00-\udfff]|" + o.replace('"', "") + ")'" + }, {token: "constant.numeric", regex: "0[xX][0-9a-fA-F]+\\b"}, { + token: "constant.numeric", + regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" + }, { + token: ["keyword", "text", "entity.name.function"], + regex: "(func)(\\s+)([a-zA-Z_$][a-zA-Z0-9_$]*)\\b" + }, { + token: function (e) { + return e[e.length - 1] == "(" ? [{ + type: s(e.slice(0, -1)) || "support.function", + value: e.slice(0, -1) + }, {type: "paren.lparen", value: e.slice(-1)}] : s(e) || "identifier" + }, regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b\\(?" + }, { + token: "keyword.operator", + regex: "!|\\$|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|==|=|!=|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=" + }, {token: "punctuation.operator", regex: "\\?|\\:|\\,|\\;|\\."}, { + token: "paren.lparen", + regex: "[[({]" + }, {token: "paren.rparen", regex: "[\\])}]"}, {token: "text", regex: "\\s+"}], + comment: [{token: "comment.end", regex: "\\*\\/", next: "start"}, {defaultToken: "comment"}], + bqstring: [{token: "string", regex: "`", next: "start"}, {defaultToken: "string"}] + }, this.embedRules(i, "doc-", [i.getEndRule("start")]) + }; + r.inherits(o, s), t.GolangHighlightRules = o +}), ace.define("ace/mode/matching_brace_outdent", ["require", "exports", "module", "ace/range"], function (e, t, n) { + "use strict"; + var r = e("../range").Range, i = function () { + }; + (function () { + this.checkOutdent = function (e, t) { + return /^\s+$/.test(e) ? /^\s*\}/.test(t) : !1 + }, this.autoOutdent = function (e, t) { + var n = e.getLine(t), i = n.match(/^(\s*\})/); + if (!i) return 0; + var s = i[1].length, o = e.findMatchingBracket({row: t, column: s}); + if (!o || o.row == t) return 0; + var u = this.$getIndent(e.getLine(o.row)); + e.replace(new r(t, 0, t, s - 1), u) + }, this.$getIndent = function (e) { + return e.match(/^\s*/)[0] + } + }).call(i.prototype), t.MatchingBraceOutdent = i +}), ace.define("ace/mode/behaviour/cstyle", ["require", "exports", "module", "ace/lib/oop", "ace/mode/behaviour", "ace/token_iterator", "ace/lib/lang"], function (e, t, n) { + "use strict"; + var r = e("../../lib/oop"), i = e("../behaviour").Behaviour, s = e("../../token_iterator").TokenIterator, + o = e("../../lib/lang"), u = ["text", "paren.rparen", "punctuation.operator"], + a = ["text", "paren.rparen", "punctuation.operator", "comment"], f, l = {}, c = function (e) { + var t = -1; + e.multiSelect && (t = e.selection.index, l.rangeCount != e.multiSelect.rangeCount && (l = {rangeCount: e.multiSelect.rangeCount})); + if (l[t]) return f = l[t]; + f = l[t] = { + autoInsertedBrackets: 0, + autoInsertedRow: -1, + autoInsertedLineEnd: "", + maybeInsertedBrackets: 0, + maybeInsertedRow: -1, + maybeInsertedLineStart: "", + maybeInsertedLineEnd: "" + } + }, h = function (e, t, n, r) { + var i = e.end.row - e.start.row; + return {text: n + t + r, selection: [0, e.start.column + 1, i, e.end.column + (i ? 0 : 1)]} + }, p = function () { + this.add("braces", "insertion", function (e, t, n, r, i) { + var s = n.getCursorPosition(), u = r.doc.getLine(s.row); + if (i == "{") { + c(n); + var a = n.getSelectionRange(), l = r.doc.getTextRange(a); + if (l !== "" && l !== "{" && n.getWrapBehavioursEnabled()) return h(a, l, "{", "}"); + if (p.isSaneInsertion(n, r)) return /[\]\}\)]/.test(u[s.column]) || n.inMultiSelectMode ? (p.recordAutoInsert(n, r, "}"), { + text: "{}", + selection: [1, 1] + }) : (p.recordMaybeInsert(n, r, "{"), {text: "{", selection: [1, 1]}) + } else if (i == "}") { + c(n); + var d = u.substring(s.column, s.column + 1); + if (d == "}") { + var v = r.$findOpeningBracket("}", {column: s.column + 1, row: s.row}); + if (v !== null && p.isAutoInsertedClosing(s, u, i)) return p.popAutoInsertedClosing(), { + text: "", + selection: [1, 1] + } + } + } else { + if (i == "\n" || i == "\r\n") { + c(n); + var m = ""; + p.isMaybeInsertedClosing(s, u) && (m = o.stringRepeat("}", f.maybeInsertedBrackets), p.clearMaybeInsertedClosing()); + var d = u.substring(s.column, s.column + 1); + if (d === "}") { + var g = r.findMatchingBracket({row: s.row, column: s.column + 1}, "}"); + if (!g) return null; + var y = this.$getIndent(r.getLine(g.row)) + } else { + if (!m) { + p.clearMaybeInsertedClosing(); + return + } + var y = this.$getIndent(u) + } + var b = y + r.getTabString(); + return {text: "\n" + b + "\n" + y + m, selection: [1, b.length, 1, b.length]} + } + p.clearMaybeInsertedClosing() + } + }), this.add("braces", "deletion", function (e, t, n, r, i) { + var s = r.doc.getTextRange(i); + if (!i.isMultiLine() && s == "{") { + c(n); + var o = r.doc.getLine(i.start.row), u = o.substring(i.end.column, i.end.column + 1); + if (u == "}") return i.end.column++, i; + f.maybeInsertedBrackets-- + } + }), this.add("parens", "insertion", function (e, t, n, r, i) { + if (i == "(") { + c(n); + var s = n.getSelectionRange(), o = r.doc.getTextRange(s); + if (o !== "" && n.getWrapBehavioursEnabled()) return h(s, o, "(", ")"); + if (p.isSaneInsertion(n, r)) return p.recordAutoInsert(n, r, ")"), {text: "()", selection: [1, 1]} + } else if (i == ")") { + c(n); + var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); + if (f == ")") { + var l = r.$findOpeningBracket(")", {column: u.column + 1, row: u.row}); + if (l !== null && p.isAutoInsertedClosing(u, a, i)) return p.popAutoInsertedClosing(), { + text: "", + selection: [1, 1] + } + } + } + }), this.add("parens", "deletion", function (e, t, n, r, i) { + var s = r.doc.getTextRange(i); + if (!i.isMultiLine() && s == "(") { + c(n); + var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); + if (u == ")") return i.end.column++, i + } + }), this.add("brackets", "insertion", function (e, t, n, r, i) { + if (i == "[") { + c(n); + var s = n.getSelectionRange(), o = r.doc.getTextRange(s); + if (o !== "" && n.getWrapBehavioursEnabled()) return h(s, o, "[", "]"); + if (p.isSaneInsertion(n, r)) return p.recordAutoInsert(n, r, "]"), {text: "[]", selection: [1, 1]} + } else if (i == "]") { + c(n); + var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); + if (f == "]") { + var l = r.$findOpeningBracket("]", {column: u.column + 1, row: u.row}); + if (l !== null && p.isAutoInsertedClosing(u, a, i)) return p.popAutoInsertedClosing(), { + text: "", + selection: [1, 1] + } + } + } + }), this.add("brackets", "deletion", function (e, t, n, r, i) { + var s = r.doc.getTextRange(i); + if (!i.isMultiLine() && s == "[") { + c(n); + var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); + if (u == "]") return i.end.column++, i + } + }), this.add("string_dquotes", "insertion", function (e, t, n, r, i) { + if (i == '"' || i == "'") { + c(n); + var s = i, o = n.getSelectionRange(), u = r.doc.getTextRange(o); + if (u !== "" && u !== "'" && u != '"' && n.getWrapBehavioursEnabled()) return h(o, u, s, s); + if (!u) { + var a = n.getCursorPosition(), f = r.doc.getLine(a.row), l = f.substring(a.column - 1, a.column), + p = f.substring(a.column, a.column + 1), d = r.getTokenAt(a.row, a.column), + v = r.getTokenAt(a.row, a.column + 1); + if (l == "\\" && d && /escape/.test(d.type)) return null; + var m = d && /string|escape/.test(d.type), g = !v || /string|escape/.test(v.type), y; + if (p == s) y = m !== g; else { + if (m && !g) return null; + if (m && g) return null; + var b = r.$mode.tokenRe; + b.lastIndex = 0; + var w = b.test(l); + b.lastIndex = 0; + var E = b.test(l); + if (w || E) return null; + if (p && !/[\s;,.})\]\\]/.test(p)) return null; + y = !0 + } + return {text: y ? s + s : "", selection: [1, 1]} + } + } + }), this.add("string_dquotes", "deletion", function (e, t, n, r, i) { + var s = r.doc.getTextRange(i); + if (!i.isMultiLine() && (s == '"' || s == "'")) { + c(n); + var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); + if (u == s) return i.end.column++, i + } + }) + }; + p.isSaneInsertion = function (e, t) { + var n = e.getCursorPosition(), r = new s(t, n.row, n.column); + if (!this.$matchTokenType(r.getCurrentToken() || "text", u)) { + var i = new s(t, n.row, n.column + 1); + if (!this.$matchTokenType(i.getCurrentToken() || "text", u)) return !1 + } + return r.stepForward(), r.getCurrentTokenRow() !== n.row || this.$matchTokenType(r.getCurrentToken() || "text", a) + }, p.$matchTokenType = function (e, t) { + return t.indexOf(e.type || e) > -1 + }, p.recordAutoInsert = function (e, t, n) { + var r = e.getCursorPosition(), i = t.doc.getLine(r.row); + this.isAutoInsertedClosing(r, i, f.autoInsertedLineEnd[0]) || (f.autoInsertedBrackets = 0), f.autoInsertedRow = r.row, f.autoInsertedLineEnd = n + i.substr(r.column), f.autoInsertedBrackets++ + }, p.recordMaybeInsert = function (e, t, n) { + var r = e.getCursorPosition(), i = t.doc.getLine(r.row); + this.isMaybeInsertedClosing(r, i) || (f.maybeInsertedBrackets = 0), f.maybeInsertedRow = r.row, f.maybeInsertedLineStart = i.substr(0, r.column) + n, f.maybeInsertedLineEnd = i.substr(r.column), f.maybeInsertedBrackets++ + }, p.isAutoInsertedClosing = function (e, t, n) { + return f.autoInsertedBrackets > 0 && e.row === f.autoInsertedRow && n === f.autoInsertedLineEnd[0] && t.substr(e.column) === f.autoInsertedLineEnd + }, p.isMaybeInsertedClosing = function (e, t) { + return f.maybeInsertedBrackets > 0 && e.row === f.maybeInsertedRow && t.substr(e.column) === f.maybeInsertedLineEnd && t.substr(0, e.column) == f.maybeInsertedLineStart + }, p.popAutoInsertedClosing = function () { + f.autoInsertedLineEnd = f.autoInsertedLineEnd.substr(1), f.autoInsertedBrackets-- + }, p.clearMaybeInsertedClosing = function () { + f && (f.maybeInsertedBrackets = 0, f.maybeInsertedRow = -1) + }, r.inherits(p, i), t.CstyleBehaviour = p +}), ace.define("ace/mode/folding/cstyle", ["require", "exports", "module", "ace/lib/oop", "ace/range", "ace/mode/folding/fold_mode"], function (e, t, n) { + "use strict"; + var r = e("../../lib/oop"), i = e("../../range").Range, s = e("./fold_mode").FoldMode, + o = t.FoldMode = function (e) { + e && (this.foldingStartMarker = new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + e.start)), this.foldingStopMarker = new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + e.end))) + }; + r.inherits(o, s), function () { + this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/, this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/, this.singleLineBlockCommentRe = /^\s*(\/\*).*\*\/\s*$/, this.tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/, this.startRegionRe = /^\s*(\/\*|\/\/)#?region\b/, this._getFoldWidgetBase = this.getFoldWidget, this.getFoldWidget = function (e, t, n) { + var r = e.getLine(n); + if (this.singleLineBlockCommentRe.test(r) && !this.startRegionRe.test(r) && !this.tripleStarBlockCommentRe.test(r)) return ""; + var i = this._getFoldWidgetBase(e, t, n); + return !i && this.startRegionRe.test(r) ? "start" : i + }, this.getFoldWidgetRange = function (e, t, n, r) { + var i = e.getLine(n); + if (this.startRegionRe.test(i)) return this.getCommentRegionBlock(e, i, n); + var s = i.match(this.foldingStartMarker); + if (s) { + var o = s.index; + if (s[1]) return this.openingBracketBlock(e, s[1], n, o); + var u = e.getCommentFoldRange(n, o + s[0].length, 1); + return u && !u.isMultiLine() && (r ? u = this.getSectionRange(e, n) : t != "all" && (u = null)), u + } + if (t === "markbegin") return; + var s = i.match(this.foldingStopMarker); + if (s) { + var o = s.index + s[0].length; + return s[1] ? this.closingBracketBlock(e, s[1], n, o) : e.getCommentFoldRange(n, o, -1) + } + }, this.getSectionRange = function (e, t) { + var n = e.getLine(t), r = n.search(/\S/), s = t, o = n.length; + t += 1; + var u = t, a = e.getLength(); + while (++t < a) { + n = e.getLine(t); + var f = n.search(/\S/); + if (f === -1) continue; + if (r > f) break; + var l = this.getFoldWidgetRange(e, "all", t); + if (l) { + if (l.start.row <= s) break; + if (l.isMultiLine()) t = l.end.row; else if (r == f) break + } + u = t + } + return new i(s, o, u, e.getLine(u).length) + }, this.getCommentRegionBlock = function (e, t, n) { + var r = t.search(/\s*$/), s = e.getLength(), o = n, u = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/, a = 1; + while (++n < s) { + t = e.getLine(n); + var f = u.exec(t); + if (!f) continue; + f[1] ? a-- : a++; + if (!a) break + } + var l = n; + if (l > o) return new i(o, r, l, t.length) + } + }.call(o.prototype) +}), ace.define("ace/mode/golang", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/mode/golang_highlight_rules", "ace/mode/matching_brace_outdent", "ace/mode/behaviour/cstyle", "ace/mode/folding/cstyle"], function (e, t, n) { + var r = e("../lib/oop"), i = e("./text").Mode, s = e("./golang_highlight_rules").GolangHighlightRules, + o = e("./matching_brace_outdent").MatchingBraceOutdent, u = e("./behaviour/cstyle").CstyleBehaviour, + a = e("./folding/cstyle").FoldMode, f = function () { + this.HighlightRules = s, this.$outdent = new o, this.foldingRules = new a, this.$behaviour = new u + }; + r.inherits(f, i), function () { + this.lineCommentStart = "//", this.blockComment = { + start: "/*", + end: "*/" + }, this.getNextLineIndent = function (e, t, n) { + var r = this.$getIndent(t), i = this.getTokenizer().getLineTokens(t, e), s = i.tokens, o = i.state; + if (s.length && s[s.length - 1].type == "comment") return r; + if (e == "start") { + var u = t.match(/^.*[\{\(\[]\s*$/); + u && (r += n) + } + return r + }, this.checkOutdent = function (e, t, n) { + return this.$outdent.checkOutdent(t, n) + }, this.autoOutdent = function (e, t, n) { + this.$outdent.autoOutdent(t, n) + }, this.$id = "ace/mode/golang" + }.call(f.prototype), t.Mode = f +}) \ No newline at end of file diff --git a/internal/server/static/ace/snippets/ golang.js b/internal/server/static/ace/snippets/ golang.js new file mode 100644 index 0000000..68b5ad5 --- /dev/null +++ b/internal/server/static/ace/snippets/ golang.js @@ -0,0 +1,4 @@ +ace.define("ace/snippets/golang", ["require", "exports", "module"], function (e, t, n) { + "use strict"; + t.snippetText = undefined, t.scope = "golang" +}) \ No newline at end of file diff --git a/internal/server/static/ace/theme-tomorrow_night_eighties.js b/internal/server/static/ace/theme-tomorrow_night_eighties.js new file mode 100644 index 0000000..d8b8660 --- /dev/null +++ b/internal/server/static/ace/theme-tomorrow_night_eighties.js @@ -0,0 +1,5 @@ +ace.define("ace/theme/tomorrow_night_eighties", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { + t.isDark = !0, t.cssClass = "ace-tomorrow-night-eighties", t.cssText = ".ace-tomorrow-night-eighties .ace_gutter {background: #272727;color: #CCC}.ace-tomorrow-night-eighties .ace_print-margin {width: 1px;background: #272727}.ace-tomorrow-night-eighties {background-color: #2D2D2D;color: #CCCCCC}.ace-tomorrow-night-eighties .ace_constant.ace_other,.ace-tomorrow-night-eighties .ace_cursor {color: #CCCCCC}.ace-tomorrow-night-eighties .ace_marker-layer .ace_selection {background: #515151}.ace-tomorrow-night-eighties.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #2D2D2D;}.ace-tomorrow-night-eighties .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-tomorrow-night-eighties .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #6A6A6A}.ace-tomorrow-night-bright .ace_stack {background: rgb(66, 90, 44)}.ace-tomorrow-night-eighties .ace_marker-layer .ace_active-line {background: #393939}.ace-tomorrow-night-eighties .ace_gutter-active-line {background-color: #393939}.ace-tomorrow-night-eighties .ace_marker-layer .ace_selected-word {border: 1px solid #515151}.ace-tomorrow-night-eighties .ace_invisible {color: #6A6A6A}.ace-tomorrow-night-eighties .ace_keyword,.ace-tomorrow-night-eighties .ace_meta,.ace-tomorrow-night-eighties .ace_storage,.ace-tomorrow-night-eighties .ace_storage.ace_type,.ace-tomorrow-night-eighties .ace_support.ace_type {color: #CC99CC}.ace-tomorrow-night-eighties .ace_keyword.ace_operator {color: #66CCCC}.ace-tomorrow-night-eighties .ace_constant.ace_character,.ace-tomorrow-night-eighties .ace_constant.ace_language,.ace-tomorrow-night-eighties .ace_constant.ace_numeric,.ace-tomorrow-night-eighties .ace_keyword.ace_other.ace_unit,.ace-tomorrow-night-eighties .ace_support.ace_constant,.ace-tomorrow-night-eighties .ace_variable.ace_parameter {color: #F99157}.ace-tomorrow-night-eighties .ace_invalid {color: #CDCDCD;background-color: #F2777A}.ace-tomorrow-night-eighties .ace_invalid.ace_deprecated {color: #CDCDCD;background-color: #CC99CC}.ace-tomorrow-night-eighties .ace_fold {background-color: #6699CC;border-color: #CCCCCC}.ace-tomorrow-night-eighties .ace_entity.ace_name.ace_function,.ace-tomorrow-night-eighties .ace_support.ace_function,.ace-tomorrow-night-eighties .ace_variable {color: #6699CC}.ace-tomorrow-night-eighties .ace_support.ace_class,.ace-tomorrow-night-eighties .ace_support.ace_type {color: #FFCC66}.ace-tomorrow-night-eighties .ace_heading,.ace-tomorrow-night-eighties .ace_markup.ace_heading,.ace-tomorrow-night-eighties .ace_string {color: #99CC99}.ace-tomorrow-night-eighties .ace_comment {color: #999999}.ace-tomorrow-night-eighties .ace_entity.ace_name.ace_tag,.ace-tomorrow-night-eighties .ace_entity.ace_other.ace_attribute-name,.ace-tomorrow-night-eighties .ace_meta.ace_tag,.ace-tomorrow-night-eighties .ace_variable {color: #F2777A}.ace-tomorrow-night-eighties .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ09NrYAgMjP4PAAtGAwchHMyAAAAAAElFTkSuQmCC) right repeat-y}"; + var r = e("../lib/dom"); + r.importCssString(t.cssText, t.cssClass) +}) \ No newline at end of file diff --git a/internal/server/static/index.html b/internal/server/static/index.html new file mode 100644 index 0000000..2e50045 --- /dev/null +++ b/internal/server/static/index.html @@ -0,0 +1,28 @@ + + + + + + Monkey Playground + + + +
+
+ Monkey + ⌘/Ctrl + ENTER to Run. ⌘/Ctrl + SPACE to Format. +
+
+
+

+        
+
+
+
+ + + + + \ No newline at end of file diff --git a/internal/server/static/main.js b/internal/server/static/main.js new file mode 100644 index 0000000..935d07e --- /dev/null +++ b/internal/server/static/main.js @@ -0,0 +1,79 @@ +(function () { + var editor = ace.edit('code'); + editor.setTheme('ace/theme/tomorrow_night_eighties'); + editor.session.setMode('ace/mode/golang'); + editor.focus(); + + var localStorage = window.localStorage || { + setItem: function () { + }, getItem: function () { + return ''; + } + }; + + var savedCode = localStorage.getItem('code') || ''; + if (savedCode === '') { + savedCode = 'println("Hello World!")\n'; + } + editor.setValue(savedCode, 1); + + $('#code').on('keydown', function (e) { + var meta = e.metaKey || e.ctrlKey; + var keyCode = (e.keyCode || e.which); + var enter = keyCode === 10 || keyCode === 13; + var format = keyCode === 32; + + if (meta && format) { + e.preventDefault(); + $('#output').html('

Formatting...

'); + $.ajax({ + url: '/api/format', + method: 'POST', + data: { + code: editor.getValue() + }, + success: function (data) { + editor.setValue(data, 1); + $('#output').html(''); + }, + error: function (xhr, status, text) { + var response = xhr.responseText.replace(/\n/g, '
'); + if (response) { + $('#output').html('

' + response + '

'); + localStorage.setItem('code', editor.getValue()); + } else { + $('#output').html('

Looks like the server is not reachable.

'); + } + } + }); + } + + if (meta && enter) { + e.preventDefault(); + $('#output').html(''); + $('#output').append('

Executing...

'); + $.ajax({ + url: '/api/run', + method: 'POST', + data: { + code: editor.getValue() + }, + success: function (data) { + var output = data.replace(/\n/g, '
'); + $('#output').html('

' + output + '

'); + localStorage.setItem('code', editor.getValue()); + }, + error: function (xhr, status, text) { + var response = xhr.responseText.replace(/\n/g, '
'); + if (response) { + $('#output').html('

' + response + '

'); + localStorage.setItem('code', editor.getValue()); + } else { + $('#output').html('

Looks like the server is not reachable.

'); + } + } + }); + } + }); + +})(); \ No newline at end of file diff --git a/internal/server/static/style.css b/internal/server/static/style.css new file mode 100644 index 0000000..602375b --- /dev/null +++ b/internal/server/static/style.css @@ -0,0 +1,66 @@ +@import url("//fonts.googleapis.com/css?family=Ubuntu+Mono"); +html, body { + background: #333; + font-size: 16px; + color: #FFF; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; } + +.layout { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; } +.layout .top { + flex-basis: 50px; + background: #222; + font-size: 20px; + display: flex; + flex-direction: row; } +.layout .top b { + flex: 1; + padding-left: 20px; + line-height: 50px; + font-family: 'Ubuntu Mono', monospace; } +.layout .top span { + flex: 1; + text-align: right; + line-height: 50px; + color: #555; + padding-right: 20px; + font-family: 'Ubuntu Mono', monospace; } +.layout .bottom { + flex: 1; + display: flex; + flex-direction: row; } +.layout .bottom .left { + background: #383838; + width: 50%; + position: relative; } +.layout .bottom .left #code { + margin: 0; + padding: 0; + font-size: 14px; + margin: 0; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; } +.layout .bottom .right { + width: 50%; + padding: 20px; + font-family: 'Ubuntu Mono', monospace; } +.layout .bottom .right p { + margin-top: 0; } +.layout .bottom .right .ide { + color: #555; } +.layout .bottom .right .msg { + color: #FFC107; } +.layout .bottom .right .msg-err { + color: #F44336; } + +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/internal/server/static/style.css.map b/internal/server/static/style.css.map new file mode 100644 index 0000000..d83878e --- /dev/null +++ b/internal/server/static/style.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AAAQ,4DAAoD;AAE5D,UAAW;EACV,UAAU,EAAE,IAAI;EAChB,SAAS,EAAE,IAAI;EACf,KAAK,EAAE,IAAI;EACX,KAAK,EAAE,IAAI;EAAE,MAAM,EAAE,IAAI;EACzB,MAAM,EAAE,CAAC;EAAE,OAAO,EAAE,CAAC;EACrB,QAAQ,EAAE,MAAM;;AAGjB,OAAQ;EACP,OAAO,EAAE,IAAI;EACb,cAAc,EAAE,MAAM;EACtB,KAAK,EAAE,IAAI;EAAE,MAAM,EAAE,IAAI;EACzB,YAAK;IACJ,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,GAAG;IAEnB,cAAE;MACD,IAAI,EAAE,CAAC;MACP,YAAY,EAAE,IAAI;MAClB,WAAW,EAAE,IAAI;MACjB,WAAW,EAAE,wBAAwB;IAGtC,iBAAK;MACJ,IAAI,EAAE,CAAC;MACP,UAAU,EAAE,KAAK;MACjB,WAAW,EAAE,IAAI;MACjB,KAAK,EAAE,IAAI;MACX,aAAa,EAAE,IAAI;MACnB,WAAW,EAAE,wBAAwB;EAIvC,eAAQ;IACP,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,GAAG;IAEnB,qBAAM;MACL,UAAU,EAAE,OAAO;MACnB,KAAK,EAAE,GAAG;MACV,QAAQ,EAAE,QAAQ;MAElB,2BAAM;QACL,MAAM,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;QACrB,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,CAAC;QAAE,QAAQ,EAAE,QAAQ;QAC7B,GAAG,EAAE,CAAC;QAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,CAAC;QAAE,KAAK,EAAE,CAAC;IAInB,sBAAO;MACN,KAAK,EAAE,GAAG;MACV,OAAO,EAAE,IAAI;MACb,WAAW,EAAE,wBAAwB;MAErC,wBAAE;QACD,UAAU,EAAE,CAAC;MAGd,2BAAK;QACJ,KAAK,EAAE,IAAI;MAGZ,2BAAK;QACJ,KAAK,EAAE,OAAO;MAGZ,+BAAS;QACP,KAAK,EAAE,OAAO", +"sources": ["style.scss"], +"names": [], +"file": "style.css" +} \ No newline at end of file diff --git a/internal/server/static/style.scss b/internal/server/static/style.scss new file mode 100644 index 0000000..12e516d --- /dev/null +++ b/internal/server/static/style.scss @@ -0,0 +1,81 @@ +@import url('//fonts.googleapis.com/css?family=Ubuntu+Mono'); + +html, body { + background: #333; + font-size: 16px; + color: #FFF; + width: 100%; height: 100%; + margin: 0; padding: 0; + overflow: hidden; +} + +.layout { + display: flex; + flex-direction: column; + width: 100%; height: 100%; + .top { + flex-basis: 50px; + background: #222; + font-size: 20px; + display: flex; + flex-direction: row; + + b { + flex: 1; + padding-left: 20px; + line-height: 50px; + font-family: 'Ubuntu Mono', monospace; + } + + span { + flex: 1; + text-align: right; + line-height: 50px; + color: #555; + padding-right: 20px; + font-family: 'Ubuntu Mono', monospace; + } + } + + .bottom { + flex: 1; + display: flex; + flex-direction: row; + + .left { + background: #383838; + width: 50%; + position: relative; + + #code { + margin: 0; padding: 0; + font-size: 14px; + margin: 0; position: absolute; + top: 0; bottom: 0; + left: 0; right: 0; + } + } + + .right { + width: 50%; + padding: 20px; + font-family: 'Ubuntu Mono', monospace; + + p { + margin-top: 0; + } + + .ide { + color: #555; + } + + .msg { + color: #FFC107; + } + + .msg-err { + color: #F44336; + } + } + } +} \ No newline at end of file diff --git a/internal/vm/vm.go b/internal/vm/vm.go index 35a299f..5396c2e 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -6,6 +6,7 @@ import ( "monkey/internal/builtins" "monkey/internal/code" "monkey/internal/compiler" + "monkey/internal/context" "monkey/internal/lexer" "monkey/internal/object" "monkey/internal/parser" @@ -37,7 +38,6 @@ func isTruthy(obj object.Object) bool { } } -// executeModule compiles the named module and returns a object.Module object func executeModule(name string, state *State) (object.Object, error) { filename := utils.FindModule(name) if filename == "" { @@ -66,7 +66,7 @@ func executeModule(name string, state *State) (object.Object, error) { code := c.Bytecode() state.Constants = code.Constants - machine := NewWithState(fmt.Sprintf("", name), code, state) + machine := New(fmt.Sprintf("", name), code, WithState(state)) err = machine.Run() if err != nil { return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err) @@ -113,9 +113,10 @@ func (s *State) ExportedHash() *object.Hash { } type VM struct { - Debug bool - Trace bool + debug bool + trace bool + ctx context.Context state *State dir string @@ -150,8 +151,26 @@ func (vm *VM) popFrame() frame { return vm.frames[vm.fp] } +type Option func(*VM) + +func WithContext(ctx context.Context) Option { + return func(vm *VM) { vm.ctx = ctx } +} + +func WithDebug(debug bool) Option { + return func(vm *VM) { vm.debug = debug } +} + +func WithState(state *State) Option { + return func(vm *VM) { vm.state = state } +} + +func WithTrace(trace bool) Option { + return func(vm *VM) { vm.trace = trace } +} + // New constructs a new monkey-lang bytecode virtual machine -func New(fn string, bytecode *compiler.Bytecode) *VM { +func New(fn string, bytecode *compiler.Bytecode, options ...Option) *VM { mainFn := object.CompiledFunction{Instructions: bytecode.Instructions} mainClosure := object.Closure{Fn: &mainFn} mainFrame := newFrame(&mainClosure, 0) @@ -159,10 +178,13 @@ func New(fn string, bytecode *compiler.Bytecode) *VM { frames := make([]frame, maxFrames) frames[0] = mainFrame + ctx := context.New() + state := NewState() state.Constants = bytecode.Constants vm := &VM{ + ctx: ctx, state: state, stack: make([]object.Object, maxStackSize), @@ -172,27 +194,8 @@ func New(fn string, bytecode *compiler.Bytecode) *VM { fp: 1, } - vm.dir, vm.file = filepath.Split(fn) - - return vm -} - -func NewWithState(fn string, bytecode *compiler.Bytecode, state *State) *VM { - mainFn := object.CompiledFunction{Instructions: bytecode.Instructions} - mainClosure := object.Closure{Fn: &mainFn} - mainFrame := newFrame(&mainClosure, 0) - - frames := make([]frame, maxFrames) - frames[0] = mainFrame - - vm := &VM{ - state: state, - - frames: frames, - fp: 1, - - stack: make([]object.Object, maxStackSize), - sp: 0, + for _, option := range options { + option(vm) } vm.dir, vm.file = filepath.Split(fn) @@ -725,7 +728,7 @@ func (vm *VM) callClosure(cl *object.Closure, 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...) + result := builtin.Fn(vm.ctx, args...) vm.sp = vm.sp - numArgs - 1 if result != nil { @@ -780,7 +783,7 @@ func (vm *VM) Run() (err error) { opcodeFreqs = make(map[code.Opcode]int) ) - if vm.Debug { + if vm.debug { start := time.Now() defer func() { end := time.Now().Sub(start) @@ -807,7 +810,7 @@ func (vm *VM) Run() (err error) { for err == nil { op = vm.currentFrame().ReadNextOp() - if vm.Debug { + if vm.debug { opcodeFreqs[op]++ log.Printf( "%-25s %-20s\n", @@ -977,7 +980,7 @@ func (vm *VM) Run() (err error) { err = fmt.Errorf("unhandled opcode: %s", op) } - if vm.Trace { + if vm.trace { log.Printf( "%-25s [ip=%02d fp=%02d, sp=%02d]", "", vm.currentFrame().ip, vm.fp-1, vm.sp, diff --git a/monkey.go b/monkey.go index e6ac612..ee505c3 100644 --- a/monkey.go +++ b/monkey.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "log" - "monkey/internal/object" "os" "path/filepath" "runtime" @@ -18,7 +17,7 @@ import ( ) // MonkeyVersion is the version number for the monkey language virtual machine. -const MonkeyVersion = "v0.0.1" +var MonkeyVersion = "v2.0.1" func fileExists(filename string) bool { info, err := os.Stat(filename) @@ -94,7 +93,7 @@ func compileString(input string, debug bool) (bc *compiler.Bytecode, err error) } // ExecFile executes a Monkey program from a file on disk. -func ExecFile(fn string, args []string, debug, trace bool) error { +func ExecFile(fn string, opts *Options) error { var ( bytecode *compiler.Bytecode err error @@ -112,13 +111,13 @@ func ExecFile(fn string, args []string, debug, trace bool) error { if err != nil { return err } - bytecode, err = compileString(string(input), debug) + bytecode, err = compileString(string(input), opts.Debug) } if err != nil { return err } - if debug { + if opts.Debug { log.Printf("Bytecode:\n%s\n", bytecode) } @@ -129,11 +128,7 @@ func ExecFile(fn string, args []string, debug, trace bool) error { } } - object.Args = args - - mvm := vm.New(fn, bytecode) - mvm.Debug = debug - mvm.Trace = trace + mvm := vm.New(fn, bytecode, vm.WithDebug(opts.Debug), vm.WithTrace(opts.Trace)) if err := mvm.Run(); err != nil { return err } @@ -142,19 +137,17 @@ func ExecFile(fn string, args []string, debug, trace bool) error { } // ExecString executes a Monkey program from a string. -func ExecString(input string, debug, trace bool) error { - bytecode, err := compileString(input, debug) +func ExecString(input string, opts *Options) error { + bytecode, err := compileString(input, opts.Debug) if err != nil { return err } - if debug { + if opts.Debug { log.Printf("Bytecode:\n%s\n", bytecode) } - mvm := vm.New("", bytecode) - mvm.Debug = debug - mvm.Trace = trace + mvm := vm.New("", bytecode, opts.ToVMOptions()...) if err = mvm.Run(); err != nil { return err } @@ -163,7 +156,7 @@ func ExecString(input string, debug, trace bool) error { } // CompileFiles compiles multiple Monkey files and returns any errors encountered during compilation. -func CompileFiles(fns []string, debug bool) error { +func CompileFiles(fns []string, opts *Options) error { for _, fn := range fns { ext := filepath.Ext(fn) mc := fn[:len(fn)-len(ext)] + ".mc" @@ -184,17 +177,17 @@ func CompileFiles(fns []string, debug bool) error { return errors.New(buf.String()) } - if debug { + if opts.Debug { log.Printf("AST:\n%s\n", res) } c := compiler.New() - c.Debug = debug + c.Debug = opts.Debug if err := c.Compile(res); err != nil { return err } - if debug { + if opts.Debug { log.Printf("Bytecode:\n%s\n", c.Bytecode()) } diff --git a/options.go b/options.go new file mode 100644 index 0000000..9ad87e7 --- /dev/null +++ b/options.go @@ -0,0 +1,34 @@ +package monkey + +import ( + "context" + "io" + "monkey/internal/vm" +) + +type Options struct { + Debug bool + Trace bool + + Args []string + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + Exit func(int) +} + +func (opts Options) ToVMOptions() []vm.Option { + ctx := context.New( + context.WithArgs(opts.Args), + context.WithStdin(opts.Stdin), + context.WithStdout(opts.Stdout), + context.WithStderr(opts.Stderr), + context.WithExit(opts.Exit), + ) + + return []vm.Option{ + vm.WithDebug(opts.Debug), + vm.WithDebug(opts.Trace), + vm.WithContext(ctx), + } +} diff --git a/repl.go b/repl.go index d8a0828..9c352ee 100644 --- a/repl.go +++ b/repl.go @@ -6,6 +6,7 @@ import ( "github.com/tebeka/atexit" "io" "log" + "monkey/internal/context" "os" "strings" @@ -21,10 +22,7 @@ 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, debug, trace bool) error { - var state = vm.NewState() - - object.Args = args +func REPL(args []string, opts *Options) error { initState, err := term.MakeRaw(0) if err != nil { @@ -44,9 +42,14 @@ func REPL(args []string, debug, trace bool) error { t := term.NewTerminal(os.Stdin, ">>> ") t.AutoCompleteCallback = autoComplete - object.Args = args - object.Stdout = t - object.ExitFunction = atexit.Exit + ctx := context.New( + context.WithArgs(args), + context.WithStdout(t), + context.WithStderr(t), + context.WithExit(atexit.Exit), + ) + + state := vm.NewState() PrintVersionInfo(t) for { @@ -67,25 +70,30 @@ func REPL(args []string, debug, trace bool) error { continue } - if debug { + if opts.Debug { log.Printf("AST:\n%s\n", res) } c := compiler.NewWithState(state.Symbols, &state.Constants) - c.Debug = debug + c.Debug = opts.Debug c.SetFileInfo("", input) if err := c.Compile(res); err != nil { fmt.Fprintln(t, err) continue } - if debug { + if opts.Debug { log.Printf("Bytecode:\n%s\n", c.Bytecode()) } - mvm := vm.NewWithState("", c.Bytecode(), state) - mvm.Debug = debug - mvm.Trace = trace + opts := []vm.Option{ + vm.WithContext(ctx), + vm.WithDebug(opts.Debug), + vm.WithTrace(opts.Trace), + vm.WithState(state), + } + + mvm := vm.New("", c.Bytecode(), opts...) if err := mvm.Run(); err != nil { fmt.Fprintf(t, "runtime error: %v\n", err) @@ -144,11 +152,8 @@ 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, debug, trace bool) { - var ( - state = vm.NewState() - reader = bufio.NewReader(os.Stdin) - ) +func SimpleREPL(args []string, opts *Options) { + var reader = bufio.NewReader(os.Stdin) defer func() { if err := recover(); err != nil { @@ -159,9 +164,14 @@ func SimpleREPL(args []string, debug, trace bool) { t := term.NewTerminal(os.Stdin, ">>> ") t.AutoCompleteCallback = autoComplete - object.Args = args - object.Stdout = t - object.ExitFunction = atexit.Exit + ctx := context.New( + context.WithArgs(args), + context.WithStdout(t), + context.WithStderr(t), + context.WithExit(atexit.Exit), + ) + + state := vm.NewState() PrintVersionInfo(os.Stdout) for { @@ -183,25 +193,30 @@ func SimpleREPL(args []string, debug, trace bool) { continue } - if debug { + if opts.Debug { log.Printf("AST:\n%s\n", res) } c := compiler.NewWithState(state.Symbols, &state.Constants) - c.Debug = debug + c.Debug = opts.Debug c.SetFileInfo("", input) if err := c.Compile(res); err != nil { fmt.Println(err) continue } - if debug { + if opts.Debug { log.Printf("Bytecode:\n%s\n", c.Bytecode()) } - mvm := vm.NewWithState("", c.Bytecode(), state) - mvm.Debug = debug - mvm.Trace = trace + opts := []vm.Option{ + vm.WithContext(ctx), + vm.WithDebug(opts.Debug), + vm.WithTrace(opts.Trace), + vm.WithState(state), + } + + mvm := vm.New("", c.Bytecode(), opts...) if err := mvm.Run(); err != nil { fmt.Printf("runtime error: %v\n", err) diff --git a/tools/release.sh b/tools/release.sh index a18742b..478fb0d 100644 --- a/tools/release.sh +++ b/tools/release.sh @@ -1,16 +1,19 @@ -#!/bin/sh +#!/bin/bash # Get the highest tag number VERSION="$(git describe --abbrev=0 --tags)" VERSION=${VERSION:-'0.0.0'} # Get number parts -MAJOR="${VERSION%%.*}"; VERSION="${VERSION#*.}" -MINOR="${VERSION%%.*}"; VERSION="${VERSION#*.}" -PATCH="${VERSION%%.*}"; VERSION="${VERSION#*.}" +MAJOR="${VERSION%%.*}" +VERSION="${VERSION#*.}" +MINOR="${VERSION%%.*}" +VERSION="${VERSION#*.}" +PATCH="${VERSION%%.*}" +VERSION="${VERSION#*.}" # Increase version -PATCH=$((PATCH+1)) +PATCH=$((PATCH + 1)) TAG="${1}" @@ -20,6 +23,10 @@ fi echo "Releasing ${TAG} ..." +git-chglog --next-tag="${TAG}" --output CHANGELOG.md +git commit -a -m "Update CHANGELOG for ${TAG}" git tag -a -s -m "Release ${TAG}" "${TAG}" -git push --tags -goreleaser release --rm-dist \ No newline at end of file +git push && git push --tags +goreleaser release \ + --rm-dist \ + --release-notes <(git-chglog "${TAG}" | tail -n+5) \ No newline at end of file