server
Some checks failed
Build / build (push) Failing after 6m4s
Test / build (push) Failing after 6m33s

This commit is contained in:
Chuck Smith
2024-04-02 12:21:41 -04:00
parent 862119e90e
commit 4c9ec5aaaa
77 changed files with 1181 additions and 244 deletions

View File

@@ -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 }}

1
.gitignore vendored
View File

@@ -93,5 +93,6 @@ fabric.properties
/dist /dist
/.vscode /.vscode
/monkey /monkey
/monkey-server
/examples/fib /examples/fib
vendor vendor

View File

@@ -6,7 +6,7 @@ builds:
flags: -tags "static_build" flags: -tags "static_build"
ldflags: >- ldflags: >-
-w -w
-X go.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}} -X gitea.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}}
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
goos: goos:
@@ -25,7 +25,7 @@ builds:
flags: -tags "static_build" flags: -tags "static_build"
ldflags: >- ldflags: >-
-w -w
-X go.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}} -X gitea.unflavoredmeson.com/monkey/v2.MonkeyVersion={{.Version}}
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
goos: goos:

0
CHANGELOG.md Normal file
View File

View File

@@ -2,7 +2,7 @@
CGO_ENABLED=0 CGO_ENABLED=0
VERSION ?= $(shell git describe 2>/dev/null || echo "") VERSION ?= $(shell git describe)
DESTDIR ?= $(GOBIN) DESTDIR ?= $(GOBIN)
@@ -33,13 +33,13 @@ build: clean cli server ## Build monkey
cli: ## Build monkey CLI cli: ## Build monkey CLI
@go build \ @go build \
-tags "netgo static_build" -installsuffix netgo \ -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/... ./cmd/monkey/...
server: ## Build Monkey server server: ## Build Monkey server
@go build \ @go build \
-tags "netgo static_build" -installsuffix netgo \ -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/... ./cmd/monkey/...
install: cli server ## Install monkey to $DESTDIR install: cli server ## Install monkey to $DESTDIR

31
cmd/monkey-server/main.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
)
var (
bind string
)
func init() {
flag.StringVar(&bind, "b", ":8000", "[interface]:<port> 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))
}

View File

@@ -23,20 +23,26 @@ func main() {
flag.BoolVar(&trace, "T", false, "Enable VM tracing") flag.BoolVar(&trace, "T", false, "Enable VM tracing")
flag.Parse() flag.Parse()
opts := &monkey.Options{
Debug: debug,
Trace: trace,
}
switch { switch {
case compile: case compile:
monkey.CompileFiles(flag.Args(), debug) monkey.CompileFiles(flag.Args(), opts)
case version: case version:
monkey.PrintVersionInfo(os.Stdout) monkey.PrintVersionInfo(os.Stdout)
case flag.NArg() > 0: 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: case simple:
monkey.SimpleREPL(flag.Args(), debug, trace) monkey.SimpleREPL(flag.Args(), opts)
default: default:
monkey.REPL(flag.Args(), debug, trace) monkey.REPL(flag.Args(), opts)
} }
} }

4
go.mod
View File

@@ -3,13 +3,17 @@ module monkey
go 1.21 go 1.21
require ( require (
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/tebeka/atexit v0.3.0 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 golang.org/x/term v0.18.0
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect 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 github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.18.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

11
go.sum
View File

@@ -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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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 h1:jleL99H7Ywt80oJKR+VWmJNnezcCOG0CuzcN3CIpsdI=
github.com/tebeka/atexit v0.3.0/go.mod h1:WJmSUSmMT7WoR7etUOaGBVXk+f5/ZJ+67qwuedq7Fbs= 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 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Abs ... // Abs ...
func Abs(args ...object.Object) object.Object { func Abs(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"abs", args, "abs", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
) )
// Accept ... // Accept ...
func Accept(args ...object.Object) object.Object { func Accept(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"accept", args, "accept", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Args ... // Args ...
func args(args ...object.Object) object.Object { func args(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"args", args, "args", args,
typing.ExactArgs(0), typing.ExactArgs(0),
@@ -14,8 +15,8 @@ func args(args ...object.Object) object.Object {
return newError(err.Error()) return newError(err.Error())
} }
elements := make([]object.Object, len(object.Args)) elements := make([]object.Object, len(ctx.Args()))
for i, arg := range object.Args { for i, arg := range ctx.Args() {
elements[i] = object.String{Value: arg} elements[i] = object.String{Value: arg}
} }
return &object.Array{Elements: elements} return &object.Array{Elements: elements}

View File

@@ -2,13 +2,14 @@ package builtins
import ( import (
"fmt" "fmt"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"os" "os"
) )
// Assert ... // Assert ...
func Assert(args ...object.Object) object.Object { func Assert(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"assert", args, "assert", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -2,13 +2,14 @@ package builtins
import ( import (
"fmt" "fmt"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strconv" "strconv"
) )
// Bin ... // Bin ...
func Bin(args ...object.Object) object.Object { func Bin(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"bin", args, "bin", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
) )
// Bind ... // Bind ...
func Bind(args ...object.Object) object.Object { func Bind(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"bind", args, "bind", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Bool ... // Bool ...
func Bool(args ...object.Object) object.Object { func Bool(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"bool", args, "bool", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -2,12 +2,13 @@ package builtins
import ( import (
"fmt" "fmt"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Chr ... // Chr ...
func Chr(args ...object.Object) object.Object { func Chr(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"chr", args, "chr", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
) )
// Close ... // Close ...
func Close(args ...object.Object) object.Object { func Close(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"close", args, "close", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
) )
// Connect ... // Connect ...
func Connect(args ...object.Object) object.Object { func Connect(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"connect", args, "connect", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Divmod ... // Divmod ...
func Divmod(args ...object.Object) object.Object { func Divmod(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"divmod", args, "divmod", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Exit ... // Exit ...
func Exit(args ...object.Object) object.Object { func Exit(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"exit", args, "exit", args,
typing.RangeOfArgs(0, 1), typing.RangeOfArgs(0, 1),
@@ -20,7 +21,7 @@ func Exit(args ...object.Object) object.Object {
status = int(args[0].(object.Integer).Value) status = int(args[0].(object.Integer).Value)
} }
object.ExitFunction(status) ctx.Exit(status)
return nil return nil
} }

View File

@@ -1,6 +1,7 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
@@ -11,7 +12,7 @@ import (
) )
// FFI ... // FFI ...
func FFI(args ...object.Object) object.Object { func FFI(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"ffi", args, "ffi", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,6 +1,7 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"sort" "sort"
@@ -11,7 +12,7 @@ import (
) )
// Find ... // Find ...
func Find(args ...object.Object) object.Object { func Find(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"find", args, "find", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// First ... // First ...
func First(args ...object.Object) object.Object { func First(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"first", args, "first", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// HashOf ... // HashOf ...
func HashOf(args ...object.Object) object.Object { func HashOf(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"hash", args, "hash", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -2,13 +2,14 @@ package builtins
import ( import (
"fmt" "fmt"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strconv" "strconv"
) )
// Hex ... // Hex ...
func Hex(args ...object.Object) object.Object { func Hex(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"hex", args, "hex", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -2,12 +2,13 @@ package builtins
import ( import (
"fmt" "fmt"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// IdOf ... // IdOf ...
func IdOf(args ...object.Object) object.Object { func IdOf(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"id", args, "id", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -4,13 +4,14 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"os" "os"
) )
// Input ... // Input ...
func Input(args ...object.Object) object.Object { func Input(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"input", args, "input", args,
typing.RangeOfArgs(0, 1), typing.RangeOfArgs(0, 1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strconv" "strconv"
) )
// Int ... // Int ...
func Int(args ...object.Object) object.Object { func Int(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"int", args, "int", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strings" "strings"
) )
// Join ... // Join ...
func Join(args ...object.Object) object.Object { func Join(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"join", args, "join", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Last ... // Last ...
func Last(args ...object.Object) object.Object { func Last(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"last", args, "last", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Len ... // Len ...
func Len(args ...object.Object) object.Object { func Len(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"len", args, "len", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
) )
// Listen ... // Listen ...
func Listen(args ...object.Object) object.Object { func Listen(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"listen", args, "listen", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strings" "strings"
) )
// Lower ... // Lower ...
func Lower(args ...object.Object) object.Object { func Lower(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"lower", args, "lower", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"sort" "sort"
) )
// Max ... // Max ...
func Max(args ...object.Object) object.Object { func Max(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"max", args, "max", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"sort" "sort"
) )
// Min ... // Min ...
func Min(args ...object.Object) object.Object { func Min(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"min", args, "min", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -2,13 +2,14 @@ package builtins
import ( import (
"fmt" "fmt"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strconv" "strconv"
) )
// Oct ... // Oct ...
func Oct(args ...object.Object) object.Object { func Oct(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"oct", args, "oct", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -3,6 +3,7 @@ package builtins
import ( import (
"fmt" "fmt"
"log" "log"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"os" "os"
@@ -51,7 +52,7 @@ func parseMode(mode string) (int, error) {
} }
// Open ... // Open ...
func Open(args ...object.Object) object.Object { func Open(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"open", args, "open", args,
typing.RangeOfArgs(1, 2), typing.RangeOfArgs(1, 2),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Ord ... // Ord ...
func Ord(args ...object.Object) object.Object { func Ord(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"ord", args, "ord", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Pop ... // Pop ...
func Pop(args ...object.Object) object.Object { func Pop(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"pop", args, "pop", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,6 +1,7 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
@@ -18,7 +19,7 @@ func pow(x, y int64) int64 {
} }
// Pow ... // Pow ...
func Pow(args ...object.Object) object.Object { func Pow(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"pow", args, "pow", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -2,12 +2,13 @@ package builtins
import ( import (
"fmt" "fmt"
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Print ... // Print ...
func Print(args ...object.Object) object.Object { func Print(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"print", args, "print", args,
typing.MinimumArgs(1), typing.MinimumArgs(1),
@@ -16,13 +17,13 @@ func Print(args ...object.Object) object.Object {
return newError(err.Error()) return newError(err.Error())
} }
fmt.Fprint(object.Stdout, args[0].String()) fmt.Fprint(ctx.Stdout(), args[0].String())
return nil return nil
} }
// Println ... // Println ...
func Println(args ...object.Object) object.Object { func Println(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"println", args, "println", args,
typing.MinimumArgs(1), typing.MinimumArgs(1),
@@ -30,7 +31,7 @@ func Println(args ...object.Object) object.Object {
return newError(err.Error()) return newError(err.Error())
} }
fmt.Fprintln(object.Stdout, args[0].String()) fmt.Fprintln(ctx.Stdout(), args[0].String())
return nil return nil
} }

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Push ... // Push ...
func Push(args ...object.Object) object.Object { func Push(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"push", args, "push", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,6 +1,7 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
@@ -10,7 +11,7 @@ import (
const DefaultBufferSize = 4096 const DefaultBufferSize = 4096
// Read ... // Read ...
func Read(args ...object.Object) object.Object { func Read(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"read", args, "read", args,
typing.RangeOfArgs(1, 2), typing.RangeOfArgs(1, 2),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"os" "os"
) )
// ReadFile ... // ReadFile ...
func ReadFile(args ...object.Object) object.Object { func ReadFile(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"readfile", args, "readfile", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Rest ... // Rest ...
func Rest(args ...object.Object) object.Object { func Rest(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"rest", args, "rest", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Reversed ... // Reversed ...
func Reversed(args ...object.Object) object.Object { func Reversed(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"reversed", args, "reversed", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
) )
// Seek ... // Seek ...
func Seek(args ...object.Object) object.Object { func Seek(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"seek", args, "seek", args,
typing.RangeOfArgs(1, 3), typing.RangeOfArgs(1, 3),

View File

@@ -1,6 +1,7 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strings" "strings"
@@ -8,7 +9,7 @@ import (
) )
// Socket ... // Socket ...
func Socket(args ...object.Object) object.Object { func Socket(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"socket", args, "socket", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"sort" "sort"
) )
// Sorted ... // Sorted ...
func Sorted(args ...object.Object) object.Object { func Sorted(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"sort", args, "sort", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strings" "strings"
) )
// Split ... // Split ...
func Split(args ...object.Object) object.Object { func Split(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"split", args, "split", args,
typing.RangeOfArgs(1, 2), typing.RangeOfArgs(1, 2),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// Str ... // Str ...
func Str(args ...object.Object) object.Object { func Str(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"str", args, "str", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,12 +1,13 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
) )
// TypeOf ... // TypeOf ...
func TypeOf(args ...object.Object) object.Object { func TypeOf(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"type", args, "type", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"strings" "strings"
) )
// Upper ... // Upper ...
func Upper(args ...object.Object) object.Object { func Upper(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"upper", args, "upper", args,
typing.ExactArgs(1), typing.ExactArgs(1),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"syscall" "syscall"
) )
// Write ... // Write ...
func Write(args ...object.Object) object.Object { func Write(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"write", args, "write", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -1,13 +1,14 @@
package builtins package builtins
import ( import (
"monkey/internal/context"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/typing" "monkey/internal/typing"
"os" "os"
) )
// WriteFile ... // WriteFile ...
func WriteFile(args ...object.Object) object.Object { func WriteFile(ctx context.Context, args ...object.Object) object.Object {
if err := typing.Check( if err := typing.Check(
"writefile", args, "writefile", args,
typing.ExactArgs(2), typing.ExactArgs(2),

View File

@@ -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
}

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"monkey/internal/ast" "monkey/internal/ast"
"monkey/internal/builtins" "monkey/internal/builtins"
"monkey/internal/context"
"monkey/internal/lexer" "monkey/internal/lexer"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/parser" "monkey/internal/parser"
@@ -25,37 +26,37 @@ func isError(obj object.Object) bool {
return false 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) { switch node := node.(type) {
// Statements // Statements
case *ast.Program: case *ast.Program:
return evalProgram(node, env) return evalProgram(node, ctx, env)
case *ast.ExpressionStatement: case *ast.ExpressionStatement:
return Eval(node.Expression, env) return Eval(node.Expression, ctx, env)
case *ast.BlockStatement: case *ast.BlockStatement:
return evalBlockStatements(node, env) return evalBlockStatements(node, ctx, env)
case *ast.IfExpression: case *ast.IfExpression:
return evalIfExpression(node, env) return evalIfExpression(node, ctx, env)
case *ast.WhileExpression: case *ast.WhileExpression:
return evalWhileExpression(node, env) return evalWhileExpression(node, ctx, env)
case *ast.ImportExpression: case *ast.ImportExpression:
return evalImportExpression(node, env) return evalImportExpression(node, ctx, env)
case *ast.ReturnStatement: case *ast.ReturnStatement:
val := Eval(node.ReturnValue, env) val := Eval(node.ReturnValue, ctx, env)
if isError(val) { if isError(val) {
return val return val
} }
return object.ReturnValue{Value: val} return object.ReturnValue{Value: val}
case *ast.BindExpression: case *ast.BindExpression:
value := Eval(node.Value, env) value := Eval(node.Value, ctx, env)
if isError(value) { if isError(value) {
return 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) return newError("expected identifier on left got=%T", node.Left)
case *ast.AssignmentExpression: case *ast.AssignmentExpression:
left := Eval(node.Left, env) left := Eval(node.Left, ctx, env)
if isError(left) { if isError(left) {
return left return left
} }
value := Eval(node.Value, env) value := Eval(node.Value, ctx, env)
if isError(value) { if isError(value) {
return 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 { if ident, ok := node.Left.(*ast.Identifier); ok {
env.Set(ident.Value, value) env.Set(ident.Value, value)
} else if ie, ok := node.Left.(*ast.IndexExpression); ok { } else if ie, ok := node.Left.(*ast.IndexExpression); ok {
obj := Eval(ie.Left, env) obj := Eval(ie.Left, ctx, env)
if isError(obj) { if isError(obj) {
return obj return obj
} }
if array, ok := obj.(*object.Array); ok { if array, ok := obj.(*object.Array); ok {
index := Eval(ie.Index, env) index := Eval(ie.Index, ctx, env)
if isError(index) { if isError(index) {
return 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) return newError("cannot index array with %#v", index)
} }
} else if hash, ok := obj.(*object.Hash); ok { } else if hash, ok := obj.(*object.Hash); ok {
key := Eval(ie.Index, env) key := Eval(ie.Index, ctx, env)
if isError(key) { if isError(key) {
return key return key
} }
@@ -139,72 +140,72 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return NULL return NULL
case *ast.PrefixExpression: case *ast.PrefixExpression:
right := Eval(node.Right, env) right := Eval(node.Right, ctx, env)
if isError(right) { if isError(right) {
return right return right
} }
return evalPrefixExpression(node.Operator, right) return evalPrefixExpression(node.Operator, right)
case *ast.InfixExpression: case *ast.InfixExpression:
left := Eval(node.Left, env) left := Eval(node.Left, ctx, env)
if isError(left) { if isError(left) {
return left return left
} }
right := Eval(node.Right, env) right := Eval(node.Right, ctx, env)
if isError(right) { if isError(right) {
return right return right
} }
return evalInfixExpression(node.Operator, left, right) return evalInfixExpression(node.Operator, left, right)
case *ast.CallExpression: case *ast.CallExpression:
function := Eval(node.Function, env) function := Eval(node.Function, ctx, env)
if isError(function) { if isError(function) {
return function return function
} }
args := evalExpressions(node.Arguments, env) args := evalExpressions(node.Arguments, ctx, env)
if len(args) == 1 && isError(args[0]) { if len(args) == 1 && isError(args[0]) {
return args[0] return args[0]
} }
return applyFunction(function, args) return applyFunction(ctx, function, args)
case *ast.StringLiteral: case *ast.StringLiteral:
return object.String{Value: node.Value} return object.String{Value: node.Value}
case *ast.ArrayLiteral: case *ast.ArrayLiteral:
elements := evalExpressions(node.Elements, env) elements := evalExpressions(node.Elements, ctx, env)
if len(elements) == 1 && isError(elements[0]) { if len(elements) == 1 && isError(elements[0]) {
return elements[0] return elements[0]
} }
return &object.Array{Elements: elements} return &object.Array{Elements: elements}
case *ast.IndexExpression: case *ast.IndexExpression:
left := Eval(node.Left, env) left := Eval(node.Left, ctx, env)
if isError(left) { if isError(left) {
return left return left
} }
index := Eval(node.Index, env) index := Eval(node.Index, ctx, env)
if isError(index) { if isError(index) {
return index return index
} }
return evalIndexExpression(left, index) return evalIndexExpression(left, index)
case *ast.HashLiteral: case *ast.HashLiteral:
return evalHashLiteral(node, env) return evalHashLiteral(node, ctx, env)
} }
return nil return nil
} }
func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) object.Object { func evalImportExpression(ie *ast.ImportExpression, ctx context.Context, env *object.Environment) object.Object {
name := Eval(ie.Name, env) name := Eval(ie.Name, ctx, env)
if isError(name) { if isError(name) {
return name return name
} }
if s, ok := name.(object.String); ok { if s, ok := name.(object.String); ok {
attrs := EvalModule(s.Value) attrs := EvalModule(ctx, s.Value)
if isError(attrs) { if isError(attrs) {
return attrs return attrs
} }
@@ -213,17 +214,17 @@ func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) obj
return newError("ImportError: invalid import path '%s'", name) 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 var result object.Object
for { for {
condition := Eval(we.Condition, env) condition := Eval(we.Condition, ctx, env)
if isError(condition) { if isError(condition) {
return condition return condition
} }
if isTruthy(condition) { if isTruthy(condition) {
result = Eval(we.Consequence, env) result = Eval(we.Consequence, ctx, env)
} else { } else {
break break
} }
@@ -236,11 +237,11 @@ func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) objec
return NULL 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 var result object.Object
for _, statement := range program.Statements { for _, statement := range program.Statements {
result = Eval(statement, env) result = Eval(statement, ctx, env)
switch result := result.(type) { switch result := result.(type) {
case object.ReturnValue: case object.ReturnValue:
@@ -254,11 +255,11 @@ func evalProgram(program *ast.Program, env *object.Environment) object.Object {
return result 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 var result object.Object
for _, statement := range block.Statements { for _, statement := range block.Statements {
result = Eval(statement, env) result = Eval(statement, ctx, env)
if result != nil { if result != nil {
rt := result.Type() 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 { func evalIfExpression(ie *ast.IfExpression, ctx context.Context, env *object.Environment) object.Object {
condition := Eval(ie.Condition, env) condition := Eval(ie.Condition, ctx, env)
if isError(condition) { if isError(condition) {
return condition return condition
} }
if isTruthy(condition) { if isTruthy(condition) {
return Eval(ie.Consequence, env) return Eval(ie.Consequence, ctx, env)
} else if ie.Alternative != nil { } else if ie.Alternative != nil {
return Eval(ie.Alternative, env) return Eval(ie.Alternative, ctx, env)
} else { } else {
return NULL 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 // 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) filename := utils.FindModule(name)
b, err := os.ReadFile(filename) b, err := os.ReadFile(filename)
@@ -526,7 +527,7 @@ func EvalModule(name string) object.Object {
} }
env := object.NewEnvironment() env := object.NewEnvironment()
Eval(module, env) Eval(module, ctx, env)
return env.ExportedHash() return env.ExportedHash()
} }
@@ -543,11 +544,11 @@ func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object
return newError("identifier not found: " + node.Value) 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 var result []object.Object
for _, e := range exps { for _, e := range exps {
evaluated := Eval(e, env) evaluated := Eval(e, ctx, env)
if isError(evaluated) { if isError(evaluated) {
return []object.Object{evaluated} return []object.Object{evaluated}
} }
@@ -557,16 +558,16 @@ func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Ob
return result 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) { switch fn := fn.(type) {
case object.Function: case object.Function:
extendedEnv := extendFunctionEnv(fn, args) extendedEnv := extendFunctionEnv(fn, args)
evaluated := Eval(fn.Body, extendedEnv) evaluated := Eval(fn.Body, ctx, extendedEnv)
return unwrapReturnValue(evaluated) return unwrapReturnValue(evaluated)
case object.Builtin: case object.Builtin:
if result := fn.Fn(args...); result != nil { if result := fn.Fn(ctx, args...); result != nil {
return result return result
} }
return NULL return NULL
@@ -655,11 +656,11 @@ func evalArrayIndexExpression(array, index object.Object) object.Object {
return arrayObject.Elements[idx] 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) pairs := make(map[object.HashKey]object.HashPair)
for keyNode, valueNode := range node.Pairs { for keyNode, valueNode := range node.Pairs {
key := Eval(keyNode, env) key := Eval(keyNode, ctx, env)
if isError(key) { if isError(key) {
return 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()) return newError("unusable as hash key: %s", key.Type())
} }
value := Eval(valueNode, env) value := Eval(valueNode, ctx, env)
if isError(value) { if isError(value) {
return value return value
} }

View File

@@ -3,6 +3,7 @@ package evaluator
import ( import (
"errors" "errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"monkey/internal/context"
"monkey/internal/lexer" "monkey/internal/lexer"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/parser" "monkey/internal/parser"
@@ -799,7 +800,7 @@ func testEval(input string) object.Object {
program := p.ParseProgram() program := p.ParseProgram()
env := object.NewEnvironment() env := object.NewEnvironment()
return Eval(program, env) return Eval(program, context.New(), env)
} }
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {

View File

@@ -1,6 +1,9 @@
package object package object
import "fmt" import (
"fmt"
"monkey/internal/context"
)
// Type represents the type of an object // Type represents the type of an object
type Type int type Type int
@@ -79,7 +82,7 @@ type Hasher interface {
} }
// BuiltinFunction represents the builtin function type // 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 { func AssertTypes(obj Object, types ...Type) bool {
for _, t := range types { for _, t := range types {

View File

@@ -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
)

View File

@@ -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)
}
}

110
internal/server/server.go Normal file
View File

@@ -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 [<address>]:<port> 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 [<address>]:<port>
// 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
}
}

View File

@@ -0,0 +1,4 @@
ace.define("ace/snippets/golang", ["require", "exports", "module"], function (e, t, n) {
"use strict";
t.snippetText = undefined, t.scope = "golang"
})

View File

@@ -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
})

View File

@@ -0,0 +1,4 @@
ace.define("ace/snippets/golang", ["require", "exports", "module"], function (e, t, n) {
"use strict";
t.snippetText = undefined, t.scope = "golang"
})

View File

@@ -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)
})

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Monkey Playground</title>
<link href="style.css" rel="stylesheet"/>
</head>
<body>
<div class="layout">
<div class="top">
<b><a href="https://git.unflavoredmeson.com/unflavoredmeson/monkey-lango"
style="color: #FFF; text-decoration: none;">Monkey</a></b>
<span>⌘/Ctrl + ENTER to Run. ⌘/Ctrl + SPACE to Format.</span>
</div>
<div class="bottom">
<div class="left">
<pre id="code"></pre>
</div>
<div id="output" class="right"></div>
</div>
</div>
<script src="ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"
integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script src="main.js"></script>
</body>
</html>

View File

@@ -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('<p class="ide">Formatting...</p>');
$.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, '<br/>');
if (response) {
$('#output').html('<p class="msg-err">' + response + '</p>');
localStorage.setItem('code', editor.getValue());
} else {
$('#output').html('<p class="msg-err">Looks like the server is not reachable.</p>');
}
}
});
}
if (meta && enter) {
e.preventDefault();
$('#output').html('');
$('#output').append('<p class="ide">Executing...</p>');
$.ajax({
url: '/api/run',
method: 'POST',
data: {
code: editor.getValue()
},
success: function (data) {
var output = data.replace(/\n/g, '<br/>');
$('#output').html('<p class="msg">' + output + '</p>');
localStorage.setItem('code', editor.getValue());
},
error: function (xhr, status, text) {
var response = xhr.responseText.replace(/\n/g, '<br/>');
if (response) {
$('#output').html('<p class="msg-err">' + response + '</p>');
localStorage.setItem('code', editor.getValue());
} else {
$('#output').html('<p class="msg-err">Looks like the server is not reachable.</p>');
}
}
});
}
});
})();

View File

@@ -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 */

View File

@@ -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"
}

View File

@@ -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;
}
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"monkey/internal/builtins" "monkey/internal/builtins"
"monkey/internal/code" "monkey/internal/code"
"monkey/internal/compiler" "monkey/internal/compiler"
"monkey/internal/context"
"monkey/internal/lexer" "monkey/internal/lexer"
"monkey/internal/object" "monkey/internal/object"
"monkey/internal/parser" "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) { func executeModule(name string, state *State) (object.Object, error) {
filename := utils.FindModule(name) filename := utils.FindModule(name)
if filename == "" { if filename == "" {
@@ -66,7 +66,7 @@ func executeModule(name string, state *State) (object.Object, error) {
code := c.Bytecode() code := c.Bytecode()
state.Constants = code.Constants state.Constants = code.Constants
machine := NewWithState(fmt.Sprintf("<module %s>", name), code, state) machine := New(fmt.Sprintf("<module %s>", name), code, WithState(state))
err = machine.Run() err = machine.Run()
if err != nil { if err != nil {
return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err) return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err)
@@ -113,9 +113,10 @@ func (s *State) ExportedHash() *object.Hash {
} }
type VM struct { type VM struct {
Debug bool debug bool
Trace bool trace bool
ctx context.Context
state *State state *State
dir string dir string
@@ -150,8 +151,26 @@ func (vm *VM) popFrame() frame {
return vm.frames[vm.fp] 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 // 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} mainFn := object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := object.Closure{Fn: &mainFn} mainClosure := object.Closure{Fn: &mainFn}
mainFrame := newFrame(&mainClosure, 0) mainFrame := newFrame(&mainClosure, 0)
@@ -159,10 +178,13 @@ func New(fn string, bytecode *compiler.Bytecode) *VM {
frames := make([]frame, maxFrames) frames := make([]frame, maxFrames)
frames[0] = mainFrame frames[0] = mainFrame
ctx := context.New()
state := NewState() state := NewState()
state.Constants = bytecode.Constants state.Constants = bytecode.Constants
vm := &VM{ vm := &VM{
ctx: ctx,
state: state, state: state,
stack: make([]object.Object, maxStackSize), stack: make([]object.Object, maxStackSize),
@@ -172,27 +194,8 @@ func New(fn string, bytecode *compiler.Bytecode) *VM {
fp: 1, fp: 1,
} }
vm.dir, vm.file = filepath.Split(fn) for _, option := range options {
option(vm)
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,
} }
vm.dir, vm.file = filepath.Split(fn) 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 { func (vm *VM) callBuiltin(builtin object.Builtin, numArgs int) error {
args := vm.stack[vm.sp-numArgs : vm.sp] args := vm.stack[vm.sp-numArgs : vm.sp]
result := builtin.Fn(args...) result := builtin.Fn(vm.ctx, args...)
vm.sp = vm.sp - numArgs - 1 vm.sp = vm.sp - numArgs - 1
if result != nil { if result != nil {
@@ -780,7 +783,7 @@ func (vm *VM) Run() (err error) {
opcodeFreqs = make(map[code.Opcode]int) opcodeFreqs = make(map[code.Opcode]int)
) )
if vm.Debug { if vm.debug {
start := time.Now() start := time.Now()
defer func() { defer func() {
end := time.Now().Sub(start) end := time.Now().Sub(start)
@@ -807,7 +810,7 @@ func (vm *VM) Run() (err error) {
for err == nil { for err == nil {
op = vm.currentFrame().ReadNextOp() op = vm.currentFrame().ReadNextOp()
if vm.Debug { if vm.debug {
opcodeFreqs[op]++ opcodeFreqs[op]++
log.Printf( log.Printf(
"%-25s %-20s\n", "%-25s %-20s\n",
@@ -977,7 +980,7 @@ func (vm *VM) Run() (err error) {
err = fmt.Errorf("unhandled opcode: %s", op) err = fmt.Errorf("unhandled opcode: %s", op)
} }
if vm.Trace { if vm.trace {
log.Printf( log.Printf(
"%-25s [ip=%02d fp=%02d, sp=%02d]", "%-25s [ip=%02d fp=%02d, sp=%02d]",
"", vm.currentFrame().ip, vm.fp-1, vm.sp, "", vm.currentFrame().ip, vm.fp-1, vm.sp,

View File

@@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"monkey/internal/object"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@@ -18,7 +17,7 @@ import (
) )
// MonkeyVersion is the version number for the monkey language virtual machine. // 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 { func fileExists(filename string) bool {
info, err := os.Stat(filename) 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. // 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 ( var (
bytecode *compiler.Bytecode bytecode *compiler.Bytecode
err error err error
@@ -112,13 +111,13 @@ func ExecFile(fn string, args []string, debug, trace bool) error {
if err != nil { if err != nil {
return err return err
} }
bytecode, err = compileString(string(input), debug) bytecode, err = compileString(string(input), opts.Debug)
} }
if err != nil { if err != nil {
return err return err
} }
if debug { if opts.Debug {
log.Printf("Bytecode:\n%s\n", bytecode) 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, vm.WithDebug(opts.Debug), vm.WithTrace(opts.Trace))
mvm := vm.New(fn, bytecode)
mvm.Debug = debug
mvm.Trace = trace
if err := mvm.Run(); err != nil { if err := mvm.Run(); err != nil {
return err return err
} }
@@ -142,19 +137,17 @@ func ExecFile(fn string, args []string, debug, trace bool) error {
} }
// ExecString executes a Monkey program from a string. // ExecString executes a Monkey program from a string.
func ExecString(input string, debug, trace bool) error { func ExecString(input string, opts *Options) error {
bytecode, err := compileString(input, debug) bytecode, err := compileString(input, opts.Debug)
if err != nil { if err != nil {
return err return err
} }
if debug { if opts.Debug {
log.Printf("Bytecode:\n%s\n", bytecode) log.Printf("Bytecode:\n%s\n", bytecode)
} }
mvm := vm.New("<stdin>", bytecode) mvm := vm.New("<stdin>", bytecode, opts.ToVMOptions()...)
mvm.Debug = debug
mvm.Trace = trace
if err = mvm.Run(); err != nil { if err = mvm.Run(); err != nil {
return err 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. // 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 { for _, fn := range fns {
ext := filepath.Ext(fn) ext := filepath.Ext(fn)
mc := fn[:len(fn)-len(ext)] + ".mc" mc := fn[:len(fn)-len(ext)] + ".mc"
@@ -184,17 +177,17 @@ func CompileFiles(fns []string, debug bool) error {
return errors.New(buf.String()) return errors.New(buf.String())
} }
if debug { if opts.Debug {
log.Printf("AST:\n%s\n", res) log.Printf("AST:\n%s\n", res)
} }
c := compiler.New() c := compiler.New()
c.Debug = debug c.Debug = opts.Debug
if err := c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
return err return err
} }
if debug { if opts.Debug {
log.Printf("Bytecode:\n%s\n", c.Bytecode()) log.Printf("Bytecode:\n%s\n", c.Bytecode())
} }

34
options.go Normal file
View File

@@ -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),
}
}

69
repl.go
View File

@@ -6,6 +6,7 @@ import (
"github.com/tebeka/atexit" "github.com/tebeka/atexit"
"io" "io"
"log" "log"
"monkey/internal/context"
"os" "os"
"strings" "strings"
@@ -21,10 +22,7 @@ import (
// by lexing, parsing and evaluating the input in the interpreter // by lexing, parsing and evaluating the input in the interpreter
// REPL provides a read-eval-print loop for the monkey virtual machine. // REPL provides a read-eval-print loop for the monkey virtual machine.
func REPL(args []string, debug, trace bool) error { func REPL(args []string, opts *Options) error {
var state = vm.NewState()
object.Args = args
initState, err := term.MakeRaw(0) initState, err := term.MakeRaw(0)
if err != nil { if err != nil {
@@ -44,9 +42,14 @@ func REPL(args []string, debug, trace bool) error {
t := term.NewTerminal(os.Stdin, ">>> ") t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete t.AutoCompleteCallback = autoComplete
object.Args = args ctx := context.New(
object.Stdout = t context.WithArgs(args),
object.ExitFunction = atexit.Exit context.WithStdout(t),
context.WithStderr(t),
context.WithExit(atexit.Exit),
)
state := vm.NewState()
PrintVersionInfo(t) PrintVersionInfo(t)
for { for {
@@ -67,25 +70,30 @@ func REPL(args []string, debug, trace bool) error {
continue continue
} }
if debug { if opts.Debug {
log.Printf("AST:\n%s\n", res) log.Printf("AST:\n%s\n", res)
} }
c := compiler.NewWithState(state.Symbols, &state.Constants) c := compiler.NewWithState(state.Symbols, &state.Constants)
c.Debug = debug c.Debug = opts.Debug
c.SetFileInfo("<stdin>", input) c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
fmt.Fprintln(t, err) fmt.Fprintln(t, err)
continue continue
} }
if debug { if opts.Debug {
log.Printf("Bytecode:\n%s\n", c.Bytecode()) log.Printf("Bytecode:\n%s\n", c.Bytecode())
} }
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state) opts := []vm.Option{
mvm.Debug = debug vm.WithContext(ctx),
mvm.Trace = trace vm.WithDebug(opts.Debug),
vm.WithTrace(opts.Trace),
vm.WithState(state),
}
mvm := vm.New("<stdin>", c.Bytecode(), opts...)
if err := mvm.Run(); err != nil { if err := mvm.Run(); err != nil {
fmt.Fprintf(t, "runtime error: %v\n", err) 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. // SimpleREPL provides a simple read-eval-print loop for the monkey virtual machine.
func SimpleREPL(args []string, debug, trace bool) { func SimpleREPL(args []string, opts *Options) {
var ( var reader = bufio.NewReader(os.Stdin)
state = vm.NewState()
reader = bufio.NewReader(os.Stdin)
)
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
@@ -159,9 +164,14 @@ func SimpleREPL(args []string, debug, trace bool) {
t := term.NewTerminal(os.Stdin, ">>> ") t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete t.AutoCompleteCallback = autoComplete
object.Args = args ctx := context.New(
object.Stdout = t context.WithArgs(args),
object.ExitFunction = atexit.Exit context.WithStdout(t),
context.WithStderr(t),
context.WithExit(atexit.Exit),
)
state := vm.NewState()
PrintVersionInfo(os.Stdout) PrintVersionInfo(os.Stdout)
for { for {
@@ -183,25 +193,30 @@ func SimpleREPL(args []string, debug, trace bool) {
continue continue
} }
if debug { if opts.Debug {
log.Printf("AST:\n%s\n", res) log.Printf("AST:\n%s\n", res)
} }
c := compiler.NewWithState(state.Symbols, &state.Constants) c := compiler.NewWithState(state.Symbols, &state.Constants)
c.Debug = debug c.Debug = opts.Debug
c.SetFileInfo("<stdin>", input) c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
fmt.Println(err) fmt.Println(err)
continue continue
} }
if debug { if opts.Debug {
log.Printf("Bytecode:\n%s\n", c.Bytecode()) log.Printf("Bytecode:\n%s\n", c.Bytecode())
} }
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state) opts := []vm.Option{
mvm.Debug = debug vm.WithContext(ctx),
mvm.Trace = trace vm.WithDebug(opts.Debug),
vm.WithTrace(opts.Trace),
vm.WithState(state),
}
mvm := vm.New("<stdin>", c.Bytecode(), opts...)
if err := mvm.Run(); err != nil { if err := mvm.Run(); err != nil {
fmt.Printf("runtime error: %v\n", err) fmt.Printf("runtime error: %v\n", err)

View File

@@ -1,13 +1,16 @@
#!/bin/sh #!/bin/bash
# Get the highest tag number # Get the highest tag number
VERSION="$(git describe --abbrev=0 --tags)" VERSION="$(git describe --abbrev=0 --tags)"
VERSION=${VERSION:-'0.0.0'} VERSION=${VERSION:-'0.0.0'}
# Get number parts # Get number parts
MAJOR="${VERSION%%.*}"; VERSION="${VERSION#*.}" MAJOR="${VERSION%%.*}"
MINOR="${VERSION%%.*}"; VERSION="${VERSION#*.}" VERSION="${VERSION#*.}"
PATCH="${VERSION%%.*}"; VERSION="${VERSION#*.}" MINOR="${VERSION%%.*}"
VERSION="${VERSION#*.}"
PATCH="${VERSION%%.*}"
VERSION="${VERSION#*.}"
# Increase version # Increase version
PATCH=$((PATCH + 1)) PATCH=$((PATCH + 1))
@@ -20,6 +23,10 @@ fi
echo "Releasing ${TAG} ..." 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 tag -a -s -m "Release ${TAG}" "${TAG}"
git push --tags git push && git push --tags
goreleaser release --rm-dist goreleaser release \
--rm-dist \
--release-notes <(git-chglog "${TAG}" | tail -n+5)