Demo changes
This commit is contained in:
96
Makefile
96
Makefile
@@ -1,93 +1,33 @@
|
|||||||
.PHONY: dev build cli server install image release profile compare bench test clean
|
.PHONY: dev build install image profile bench test clean
|
||||||
|
|
||||||
CGO_ENABLED=0
|
CGO_ENABLED=0
|
||||||
|
COMMIT=$(shell git rev-parse --short HEAD)
|
||||||
|
|
||||||
VERSION ?= $(shell git describe 2>/dev/null || echo "")
|
all: dev
|
||||||
|
|
||||||
DESTDIR ?= $(GOBIN)
|
dev: build
|
||||||
|
@./monkey-lang -d
|
||||||
|
|
||||||
ifeq ($(LOCAL), 1)
|
build: clean
|
||||||
IMAGE := r.mills.io/prologic/monkey
|
|
||||||
TAG := dev
|
|
||||||
else
|
|
||||||
ifeq ($(BRANCH), master)
|
|
||||||
IMAGE := prologic/monkey
|
|
||||||
TAG := latest
|
|
||||||
else
|
|
||||||
IMAGE := prologic/monkey
|
|
||||||
TAG := dev
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
all: help
|
|
||||||
|
|
||||||
help: ## Show this help message
|
|
||||||
@echo "monkey - Monkey Lang"
|
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
|
||||||
|
|
||||||
dev: cli ## Build monkey and run the REPL
|
|
||||||
@./monkey
|
|
||||||
|
|
||||||
build: clean cli server ## Build monkey
|
|
||||||
|
|
||||||
cli: ## Build monkey CLI
|
|
||||||
@go build \
|
@go build \
|
||||||
-tags "netgo static_build" -installsuffix netgo \
|
-tags "netgo static_build" -installsuffix netgo \
|
||||||
-ldflags "-w -X go.mills.io/monkey/v2.MonkeyVersion=$(VERSION)" \
|
-ldflags "-w -X $(shell go list)/version/.GitCommit=$(COMMIT)" \
|
||||||
./cmd/monkey/...
|
.
|
||||||
|
|
||||||
server: ## Build Monkey server
|
install: build
|
||||||
@go build \
|
@go install
|
||||||
-tags "netgo static_build" -installsuffix netgo \
|
|
||||||
-ldflags "-w -X go.mills.io/monkey/v2.MonkeyVersion=$(VERSION)" \
|
|
||||||
./cmd/monkey-server/...
|
|
||||||
|
|
||||||
install: cli server ## Install monkey to $DESTDIR
|
image:
|
||||||
@install -D -m 755 monkey $(DESTDIR)/monkey
|
@docker build -t prologic/monkey-lang .
|
||||||
@install -D -m 755 monkey-server $(DESTDIR)monkey-server
|
|
||||||
|
|
||||||
ifeq ($(PUBLISH), 1)
|
profile:
|
||||||
image: ## Build and Publish the Docker image
|
|
||||||
@docker buildx build \
|
|
||||||
--build-arg VERSION="$(VERSION)" \
|
|
||||||
--build-arg COMMIT="$(COMMIT)" \
|
|
||||||
--build-arg BUILD="$(BUILD)" \
|
|
||||||
--platform linux/amd64,linux/arm64 --push -t $(IMAGE):$(TAG) .
|
|
||||||
else
|
|
||||||
image: ## Build the Docker image
|
|
||||||
@docker build \
|
|
||||||
--build-arg VERSION="$(VERSION)" -t $(IMAGE):$(TAG) .
|
|
||||||
endif
|
|
||||||
|
|
||||||
release: ## Release monkey
|
|
||||||
@./tools/release.sh
|
|
||||||
|
|
||||||
profile: ## Run tests with profiling enabled
|
|
||||||
@go test -cpuprofile cpu.prof -memprofile mem.prof -v -bench ./...
|
@go test -cpuprofile cpu.prof -memprofile mem.prof -v -bench ./...
|
||||||
|
|
||||||
compare: ## Run benchmarks comparing Monkey with other languages
|
bench:
|
||||||
@hyperfine -w 5 -p 'make build; gcc -o examples/fib examples/fib.c' \
|
|
||||||
-n c -n go -n tengo -n python -n tauc -n taugo -n monkey \
|
|
||||||
--sort mean-time --export-markdown Benchmark.md \
|
|
||||||
'./examples/fib' \
|
|
||||||
'go run examples/fib.go' \
|
|
||||||
'tengo examples/fib.tengo' \
|
|
||||||
'python3 examples/fib.py' \
|
|
||||||
'tauc examples/fib.tau' \
|
|
||||||
'taugo examples/fib.tau' \
|
|
||||||
'./monkey examples/fib.m'
|
|
||||||
|
|
||||||
bench: # Run test benchmarks
|
|
||||||
@go test -v -benchmem -bench=. ./...
|
@go test -v -benchmem -bench=. ./...
|
||||||
|
|
||||||
test: ## Run unit tests
|
test:
|
||||||
@go test -v \
|
@go test -v -cover -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... -race ./...
|
||||||
-cover \
|
|
||||||
-coverprofile coverage.out \
|
|
||||||
-covermode atomic \
|
|
||||||
-coverpkg ./... \
|
|
||||||
-race \
|
|
||||||
./...
|
|
||||||
|
|
||||||
clean: ## Cleanup untrakced files
|
clean:
|
||||||
@git clean -f -d -X 2> /dev/null || true
|
@git clean -f -d -X
|
||||||
@@ -10,6 +10,14 @@ type Instructions []byte
|
|||||||
|
|
||||||
type Opcode byte
|
type Opcode byte
|
||||||
|
|
||||||
|
func (o Opcode) String() string {
|
||||||
|
def, err := Lookup(byte(o))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return def.Name
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OpConstant Opcode = iota
|
OpConstant Opcode = iota
|
||||||
OpAdd
|
OpAdd
|
||||||
|
|||||||
@@ -40,4 +40,4 @@ let map = fn(arr, f) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let numbers = [1, 1 + 1, 4 - 1, 2 * 2, 2 + 3, 12 / 2];
|
let numbers = [1, 1 + 1, 4 - 1, 2 * 2, 2 + 3, 12 / 2];
|
||||||
map(numbers, fibonacci);
|
//map(numbers, fibonacci);
|
||||||
8
examples/fact.monkey
Normal file
8
examples/fact.monkey
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
let fact = fn(n) {
|
||||||
|
if (n == 0) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return n * fact(n - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
print(fact(5)
|
||||||
8
examples/factt.monkey
Normal file
8
examples/factt.monkey
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
let fact = fn(n, a) {
|
||||||
|
if (n == 0) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return fact(n - 1, a * n)
|
||||||
|
}
|
||||||
|
|
||||||
|
print(fact(5, 1))
|
||||||
1
examples/hello.monkey
Normal file
1
examples/hello.monkey
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print("Hello World!")
|
||||||
1
testdata/popbug1.monkey
vendored
Normal file
1
testdata/popbug1.monkey
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
let n = 2; let x = 0; while (n > 0) { if (n > 1) { x = x + 1 }; n = n - 1; }; x;
|
||||||
1
testdata/popbug2.monkey
vendored
Normal file
1
testdata/popbug2.monkey
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
let n = 2; let x = 0; while (n > 0) { if (n > 1) { x = x + 1 }; let n = n - 1; }; x;
|
||||||
1
testdata/popbug3.monkey
vendored
Normal file
1
testdata/popbug3.monkey
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
let x = 1; if (x == 1) { let x = 2 }
|
||||||
@@ -21,3 +21,7 @@ func NewFrame(cl *object.Closure, basePointer int) *Frame {
|
|||||||
func (f *Frame) Instructions() code.Instructions {
|
func (f *Frame) Instructions() code.Instructions {
|
||||||
return f.cl.Fn.Instructions
|
return f.cl.Fn.Instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Frame) NextOp() code.Opcode {
|
||||||
|
return code.Opcode(f.Instructions()[f.ip+1])
|
||||||
|
}
|
||||||
|
|||||||
19
vm/vm.go
19
vm/vm.go
@@ -119,7 +119,11 @@ func (vm *VM) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case code.OpPop:
|
case code.OpPop:
|
||||||
vm.pop()
|
// This makes things like this work:
|
||||||
|
// >> let x = 1; if (x == 1) { x = 2 }
|
||||||
|
if vm.sp > 0 {
|
||||||
|
vm.pop()
|
||||||
|
}
|
||||||
|
|
||||||
case code.OpTrue:
|
case code.OpTrue:
|
||||||
err := vm.push(True)
|
err := vm.push(True)
|
||||||
@@ -563,6 +567,19 @@ func (vm *VM) callClosure(cl *object.Closure, numArgs int) error {
|
|||||||
return fmt.Errorf("wrong number of arguments: want=%d, got=%d", cl.Fn.NumParameters, numArgs)
|
return fmt.Errorf("wrong number of arguments: want=%d, got=%d", cl.Fn.NumParameters, numArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize tail calls and avoid a new frame
|
||||||
|
if cl.Fn == vm.currentFrame().cl.Fn {
|
||||||
|
nextOP := vm.currentFrame().NextOp()
|
||||||
|
if nextOP == code.OpReturn {
|
||||||
|
for p := 0; p < numArgs; p++ {
|
||||||
|
vm.stack[vm.currentFrame().basePointer+p] = vm.stack[vm.sp-numArgs+p]
|
||||||
|
}
|
||||||
|
vm.sp -= numArgs + 1
|
||||||
|
vm.currentFrame().ip = -1 // reset IP to the beginning of the frame
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
frame := NewFrame(cl, vm.sp-numArgs)
|
frame := NewFrame(cl, vm.sp-numArgs)
|
||||||
vm.pushFrame(frame)
|
vm.pushFrame(frame)
|
||||||
vm.sp = frame.basePointer + cl.Fn.NumLocals
|
vm.sp = frame.basePointer + cl.Fn.NumLocals
|
||||||
|
|||||||
133
vm/vm_test.go
133
vm/vm_test.go
@@ -833,6 +833,40 @@ func TestAssignmentStatements(t *testing.T) {
|
|||||||
runVmTests(t, tests)
|
runVmTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTailCalls(t *testing.T) {
|
||||||
|
tests := []vmTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let fact = fn(n, a) {
|
||||||
|
if (n == 0) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return fact(n - 1, a * n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fact(5, 1)
|
||||||
|
`,
|
||||||
|
expected: 120,
|
||||||
|
},
|
||||||
|
|
||||||
|
// without tail recursion optimization this will cause a stack overflow
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let iter = fn(n, max) {
|
||||||
|
if (n == max) {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return iter(n + 1, max)
|
||||||
|
}
|
||||||
|
iter(0, 9999)
|
||||||
|
`,
|
||||||
|
expected: 9999,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runVmTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIntegration(t *testing.T) {
|
func TestIntegration(t *testing.T) {
|
||||||
matches, err := filepath.Glob("../testdata/*.monkey")
|
matches, err := filepath.Glob("../testdata/*.monkey")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -874,42 +908,79 @@ func TestIntegration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExamples(t *testing.T) {
|
func BenchmarkFibonacci(b *testing.B) {
|
||||||
matches, err := filepath.Glob("../examples/*.monkey")
|
tests := map[string]string{
|
||||||
if err != nil {
|
"iterative": `
|
||||||
t.Error(err)
|
let fib = fn(n) {
|
||||||
|
if (n < 3) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
let a = 1
|
||||||
|
let b = 1
|
||||||
|
let c = 0
|
||||||
|
let i = 0
|
||||||
|
while (i < n - 2) {
|
||||||
|
c = a + b
|
||||||
|
b = a
|
||||||
|
a = c
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
fib(35)
|
||||||
|
`,
|
||||||
|
"recursive": `
|
||||||
|
let fib = fn(x) {
|
||||||
|
if (x == 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (x == 1) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return fib(x-1) + fib(x-2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fib(35)
|
||||||
|
`,
|
||||||
|
"tail-recursive": `
|
||||||
|
let fib = fn(n, a, b) {
|
||||||
|
if (n == 0) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
if (n == 1) {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return fib(n - 1, b, a + b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fib(35, 0, 1)
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, match := range matches {
|
for name, input := range tests {
|
||||||
basename := path.Base(match)
|
b.Run(name, func(b *testing.B) {
|
||||||
name := strings.TrimSuffix(basename, filepath.Ext(basename))
|
for i := 0; i < b.N; i++ {
|
||||||
|
program := parse(input)
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
c := compiler.New()
|
||||||
b, err := os.ReadFile(match)
|
err := c.Compile(program)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
b.Log(input)
|
||||||
}
|
b.Fatalf("compiler error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
input := string(b)
|
vm := New(c.Bytecode())
|
||||||
program := parse(input)
|
|
||||||
|
|
||||||
c := compiler.New()
|
err = vm.Run()
|
||||||
err = c.Compile(program)
|
if err != nil {
|
||||||
if err != nil {
|
b.Log(input)
|
||||||
t.Log(input)
|
b.Fatalf("vm error: %s", err)
|
||||||
t.Fatalf("compiler error: %s", err)
|
}
|
||||||
}
|
if vm.sp != 0 {
|
||||||
|
b.Log(input)
|
||||||
vm := New(c.Bytecode())
|
b.Fatal("vm stack pointer non-zero")
|
||||||
|
}
|
||||||
err = vm.Run()
|
|
||||||
if err != nil {
|
|
||||||
t.Log(input)
|
|
||||||
t.Fatalf("vm error: %s", err)
|
|
||||||
}
|
|
||||||
if vm.sp != 0 {
|
|
||||||
t.Log(input)
|
|
||||||
t.Fatal("vm stack pointer non-zero")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user