optimizations
Some checks failed
Build / build (push) Successful in 10m29s
Publish Image / publish (push) Failing after 31s
Test / build (push) Failing after 6m34s

This commit is contained in:
Chuck Smith
2024-04-02 14:08:08 -04:00
parent 4c9ec5aaaa
commit 07fd82b261
23 changed files with 296 additions and 265 deletions

View File

@@ -3,7 +3,7 @@ style: Github
template: CHANGELOG.tpl.md template: CHANGELOG.tpl.md
info: info:
title: CHANGELOG title: CHANGELOG
repository_url: https://go.unflavoredmeson.com/monkey repository_url: https://git.unflavoredmeson.com/monkey
options: options:
commits: commits:
filters: filters:

View File

@@ -0,0 +1,33 @@
---
name: Publish Image
on:
push:
branches: [ master ]
env:
REGISTRY: r.unflavoredmeson.com
IMAGE: unflavoredmeson/monkey
TAG: latest
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup Docker Buildx
uses: actions/setup-buildx@v2
- name: Login to Registry
uses: actions/docker-login@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASS }}
- name: Build and Push Image
uses: actions/docker-build-push@v4
with:
context: .
push: true
tags: ${{ env.REGISTRY}}/${{ env.IMAGE }}:${{ env.TAG }}

15
.idea/git_toolbox_prj.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"flag" "flag"
"fmt"
"monkey" "monkey"
"os" "os"
) )
@@ -24,6 +25,7 @@ func main() {
flag.Parse() flag.Parse()
opts := &monkey.Options{ opts := &monkey.Options{
Args: flag.Args(),
Debug: debug, Debug: debug,
Trace: trace, Trace: trace,
} }
@@ -37,12 +39,15 @@ func main() {
case flag.NArg() > 0: case flag.NArg() > 0:
opts.Args = flag.Args()[1:] opts.Args = flag.Args()[1:]
monkey.ExecFile(flag.Arg(0), opts) if err := monkey.ExecFile(flag.Arg(0), opts); err != nil {
fmt.Fprintf(os.Stderr, "runtime error: %v\n", err)
os.Exit(1)
}
case simple: case simple:
monkey.SimpleREPL(flag.Args(), opts) monkey.SimpleREPL(opts)
default: default:
monkey.REPL(flag.Args(), opts) monkey.REPL(opts)
} }
} }

View File

@@ -4,12 +4,13 @@ package main
import ( import (
"log" "log"
"monkey"
) )
const program = `println("Hello World!")` const program = `println("Hello World!")`
func main() { func main() {
if err := monkey.ExecString(program, false, false); err != nil { if err := monkey.ExecString(program, nil); err != nil {
log.Fatal("error running program") log.Fatal("error running program")
} }
} }

View File

@@ -36,7 +36,7 @@ func Find(ctx context.Context, args ...object.Object) object.Object {
// find in array // find in array
if haystack, ok := args[0].(*object.Array); ok { if haystack, ok := args[0].(*object.Array); ok {
needle := args[1].(object.Comparable) needle := args[1]
i := sort.Search(len(haystack.Elements), func(i int) bool { i := sort.Search(len(haystack.Elements), func(i int) bool {
return needle.Compare(haystack.Elements[i]) == 0 return needle.Compare(haystack.Elements[i]) == 0
}) })

View File

@@ -26,37 +26,37 @@ func isError(obj object.Object) bool {
return false return false
} }
func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Object { func Eval(ctx context.Context, node ast.Node, env *object.Environment) object.Object {
switch node := node.(type) { switch node := node.(type) {
// Statements // Statements
case *ast.Program: case *ast.Program:
return evalProgram(node, ctx, env) return evalProgram(ctx, node, env)
case *ast.ExpressionStatement: case *ast.ExpressionStatement:
return Eval(node.Expression, ctx, env) return Eval(ctx, node.Expression, env)
case *ast.BlockStatement: case *ast.BlockStatement:
return evalBlockStatements(node, ctx, env) return evalBlockStatements(ctx, node, env)
case *ast.IfExpression: case *ast.IfExpression:
return evalIfExpression(node, ctx, env) return evalIfExpression(ctx, node, env)
case *ast.WhileExpression: case *ast.WhileExpression:
return evalWhileExpression(node, ctx, env) return evalWhileExpression(ctx, node, env)
case *ast.ImportExpression: case *ast.ImportExpression:
return evalImportExpression(node, ctx, env) return evalImportExpression(ctx, node, env)
case *ast.ReturnStatement: case *ast.ReturnStatement:
val := Eval(node.ReturnValue, ctx, env) val := Eval(ctx, node.ReturnValue, 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, ctx, env) value := Eval(ctx, node.Value, env)
if isError(value) { if isError(value) {
return value return value
} }
@@ -73,12 +73,12 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob
return newError("expected identifier on left got=%T", node.Left) return newError("expected identifier on left got=%T", node.Left)
case *ast.AssignmentExpression: case *ast.AssignmentExpression:
left := Eval(node.Left, ctx, env) left := Eval(ctx, node.Left, env)
if isError(left) { if isError(left) {
return left return left
} }
value := Eval(node.Value, ctx, env) value := Eval(ctx, node.Value, env)
if isError(value) { if isError(value) {
return value return value
} }
@@ -86,13 +86,13 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob
if ident, ok := node.Left.(*ast.Identifier); ok { 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, ctx, env) obj := Eval(ctx, ie.Left, 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, ctx, env) index := Eval(ctx, ie.Index, env)
if isError(index) { if isError(index) {
return index return index
} }
@@ -102,7 +102,7 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob
return newError("cannot index array with %#v", index) 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, ctx, env) key := Eval(ctx, ie.Index, env)
if isError(key) { if isError(key) {
return key return key
} }
@@ -140,29 +140,29 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob
return NULL return NULL
case *ast.PrefixExpression: case *ast.PrefixExpression:
right := Eval(node.Right, ctx, env) right := Eval(ctx, node.Right, 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, ctx, env) left := Eval(ctx, node.Left, env)
if isError(left) { if isError(left) {
return left return left
} }
right := Eval(node.Right, ctx, env) right := Eval(ctx, node.Right, 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, ctx, env) function := Eval(ctx, node.Function, env)
if isError(function) { if isError(function) {
return function return function
} }
args := evalExpressions(node.Arguments, ctx, env) args := evalExpressions(ctx, node.Arguments, env)
if len(args) == 1 && isError(args[0]) { if len(args) == 1 && isError(args[0]) {
return args[0] return args[0]
} }
@@ -173,39 +173,39 @@ func Eval(node ast.Node, ctx context.Context, env *object.Environment) object.Ob
return object.String{Value: node.Value} return object.String{Value: node.Value}
case *ast.ArrayLiteral: case *ast.ArrayLiteral:
elements := evalExpressions(node.Elements, ctx, env) elements := evalExpressions(ctx, node.Elements, 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, ctx, env) left := Eval(ctx, node.Left, env)
if isError(left) { if isError(left) {
return left return left
} }
index := Eval(node.Index, ctx, env) index := Eval(ctx, node.Index, 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, ctx, env) return evalHashLiteral(ctx, node, env)
} }
return nil return nil
} }
func evalImportExpression(ie *ast.ImportExpression, ctx context.Context, env *object.Environment) object.Object { func evalImportExpression(ctx context.Context, ie *ast.ImportExpression, env *object.Environment) object.Object {
name := Eval(ie.Name, ctx, env) name := Eval(ctx, ie.Name, 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(ctx, s.Value) attrs := Module(ctx, s.Value)
if isError(attrs) { if isError(attrs) {
return attrs return attrs
} }
@@ -214,17 +214,17 @@ func evalImportExpression(ie *ast.ImportExpression, ctx context.Context, env *ob
return newError("ImportError: invalid import path '%s'", name) return newError("ImportError: invalid import path '%s'", name)
} }
func evalWhileExpression(we *ast.WhileExpression, ctx context.Context, env *object.Environment) object.Object { func evalWhileExpression(ctx context.Context, we *ast.WhileExpression, env *object.Environment) object.Object {
var result object.Object var result object.Object
for { for {
condition := Eval(we.Condition, ctx, env) condition := Eval(ctx, we.Condition, env)
if isError(condition) { if isError(condition) {
return condition return condition
} }
if isTruthy(condition) { if isTruthy(condition) {
result = Eval(we.Consequence, ctx, env) result = Eval(ctx, we.Consequence, env)
} else { } else {
break break
} }
@@ -237,11 +237,11 @@ func evalWhileExpression(we *ast.WhileExpression, ctx context.Context, env *obje
return NULL return NULL
} }
func evalProgram(program *ast.Program, ctx context.Context, env *object.Environment) object.Object { func evalProgram(ctx context.Context, program *ast.Program, env *object.Environment) object.Object {
var result object.Object var result object.Object
for _, statement := range program.Statements { for _, statement := range program.Statements {
result = Eval(statement, ctx, env) result = Eval(ctx, statement, env)
switch result := result.(type) { switch result := result.(type) {
case object.ReturnValue: case object.ReturnValue:
@@ -255,11 +255,11 @@ func evalProgram(program *ast.Program, ctx context.Context, env *object.Environm
return result return result
} }
func evalBlockStatements(block *ast.BlockStatement, ctx context.Context, env *object.Environment) object.Object { func evalBlockStatements(ctx context.Context, block *ast.BlockStatement, env *object.Environment) object.Object {
var result object.Object var result object.Object
for _, statement := range block.Statements { for _, statement := range block.Statements {
result = Eval(statement, ctx, env) result = Eval(ctx, statement, env)
if result != nil { if result != nil {
rt := result.Type() rt := result.Type()
@@ -385,17 +385,17 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje
return object.String{Value: strings.Repeat(rightVal, int(leftVal))} return object.String{Value: strings.Repeat(rightVal, int(leftVal))}
case operator == "==": case operator == "==":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 0) return nativeBoolToBooleanObject(left.Compare(right) == 0)
case operator == "!=": case operator == "!=":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) != 0) return nativeBoolToBooleanObject(left.Compare(right) != 0)
case operator == "<=": case operator == "<=":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) < 1) return nativeBoolToBooleanObject(left.Compare(right) < 1)
case operator == ">=": case operator == ">=":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) > -1) return nativeBoolToBooleanObject(left.Compare(right) > -1)
case operator == "<": case operator == "<":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == -1) return nativeBoolToBooleanObject(left.Compare(right) == -1)
case operator == ">": case operator == ">":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 1) return nativeBoolToBooleanObject(left.Compare(right) == 1)
case left.Type() == object.BooleanType && right.Type() == object.BooleanType: case left.Type() == object.BooleanType && right.Type() == object.BooleanType:
return evalBooleanInfixExpression(operator, left, right) return evalBooleanInfixExpression(operator, left, right)
@@ -477,16 +477,16 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje
} }
} }
func evalIfExpression(ie *ast.IfExpression, ctx context.Context, env *object.Environment) object.Object { func evalIfExpression(ctx context.Context, ie *ast.IfExpression, env *object.Environment) object.Object {
condition := Eval(ie.Condition, ctx, env) condition := Eval(ctx, ie.Condition, env)
if isError(condition) { if isError(condition) {
return condition return condition
} }
if isTruthy(condition) { if isTruthy(condition) {
return Eval(ie.Consequence, ctx, env) return Eval(ctx, ie.Consequence, env)
} else if ie.Alternative != nil { } else if ie.Alternative != nil {
return Eval(ie.Alternative, ctx, env) return Eval(ctx, ie.Alternative, env)
} else { } else {
return NULL return NULL
} }
@@ -509,8 +509,8 @@ func newError(format string, a ...interface{}) object.Error {
return object.Error{Message: fmt.Sprintf(format, a...)} return object.Error{Message: fmt.Sprintf(format, a...)}
} }
// EvalModule evaluates the named module and returns a object.Module objec // Module evaluates the named module and returns a object.Module object
func EvalModule(ctx context.Context, name string) object.Object { func Module(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)
@@ -527,7 +527,7 @@ func EvalModule(ctx context.Context, name string) object.Object {
} }
env := object.NewEnvironment() env := object.NewEnvironment()
Eval(module, ctx, env) Eval(ctx, module, env)
return env.ExportedHash() return env.ExportedHash()
} }
@@ -544,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, ctx context.Context, env *object.Environment) []object.Object { func evalExpressions(ctx context.Context, exps []ast.Expression, env *object.Environment) []object.Object {
var result []object.Object var result []object.Object
for _, e := range exps { for _, e := range exps {
evaluated := Eval(e, ctx, env) evaluated := Eval(ctx, e, env)
if isError(evaluated) { if isError(evaluated) {
return []object.Object{evaluated} return []object.Object{evaluated}
} }
@@ -563,7 +563,7 @@ func applyFunction(ctx context.Context, fn object.Object, args []object.Object)
case object.Function: case object.Function:
extendedEnv := extendFunctionEnv(fn, args) extendedEnv := extendFunctionEnv(fn, args)
evaluated := Eval(fn.Body, ctx, extendedEnv) evaluated := Eval(ctx, fn.Body, extendedEnv)
return unwrapReturnValue(evaluated) return unwrapReturnValue(evaluated)
case object.Builtin: case object.Builtin:
@@ -656,11 +656,11 @@ func evalArrayIndexExpression(array, index object.Object) object.Object {
return arrayObject.Elements[idx] return arrayObject.Elements[idx]
} }
func evalHashLiteral(node *ast.HashLiteral, ctx context.Context, env *object.Environment) object.Object { func evalHashLiteral(ctx context.Context, node *ast.HashLiteral, env *object.Environment) object.Object {
pairs := make(map[object.HashKey]object.HashPair) pairs := make(map[object.HashKey]object.HashPair)
for keyNode, valueNode := range node.Pairs { for keyNode, valueNode := range node.Pairs {
key := Eval(keyNode, ctx, env) key := Eval(ctx, keyNode, env)
if isError(key) { if isError(key) {
return key return key
} }
@@ -670,7 +670,7 @@ func evalHashLiteral(node *ast.HashLiteral, ctx context.Context, env *object.Env
return newError("unusable as hash key: %s", key.Type()) return newError("unusable as hash key: %s", key.Type())
} }
value := Eval(valueNode, ctx, env) value := Eval(ctx, valueNode, env)
if isError(value) { if isError(value) {
return value return value
} }

View File

@@ -800,7 +800,7 @@ func testEval(input string) object.Object {
program := p.ParseProgram() program := p.ParseProgram()
env := object.NewEnvironment() env := object.NewEnvironment()
return Eval(program, context.New(), env) return Eval(context.New(), program, env)
} }
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {

View File

@@ -63,11 +63,8 @@ func (ao *Array) String() string {
return ao.Inspect() return ao.Inspect()
} }
func (ao *Array) Less(i, j int) bool { func (a *Array) Less(i, j int) bool {
if cmp, ok := ao.Elements[i].(Comparable); ok { return a.Elements[i].Compare(a.Elements[j]) == -1
return cmp.Compare(ao.Elements[j]) == -1
}
return false
} }
func (a *Array) Add(other Object) (Object, error) { func (a *Array) Add(other Object) (Object, error) {
@@ -129,18 +126,15 @@ func (ao *Array) Set(index, other Object) error {
return nil return nil
} }
func (ao *Array) Compare(other Object) int { func (a *Array) Compare(other Object) int {
if obj, ok := other.(*Array); ok { if obj, ok := other.(*Array); ok {
if len(ao.Elements) != len(obj.Elements) { if len(a.Elements) != len(obj.Elements) {
return -1 return -1
} }
for i, el := range ao.Elements { for i, el := range a.Elements {
cmp, ok := el.(Comparable) val := el.Compare(obj.Elements[i])
if !ok { if val != 0 {
return -1 return val
}
if cmp.Compare(obj.Elements[i]) != 0 {
return cmp.Compare(obj.Elements[i])
} }
} }

View File

@@ -19,6 +19,19 @@ func (b Builtin) Inspect() string {
return fmt.Sprintf("<built-in function %s>", b.Name) return fmt.Sprintf("<built-in function %s>", b.Name)
} }
func (b Builtin) Compare(other Object) int {
if obj, ok := other.(Builtin); ok {
if b.Name > obj.Name {
return 1
}
if b.Name == obj.Name {
return 0
}
return -1
}
return -1
}
func (b Builtin) String() string { func (b Builtin) String() string {
return b.Inspect() return b.Inspect()
} }

View File

@@ -23,11 +23,16 @@ func (cf *CompiledFunction) Inspect() string {
return fmt.Sprintf("CompiledFunction[%p]", cf) return fmt.Sprintf("CompiledFunction[%p]", cf)
} }
func (cf CompiledFunction) Compare(other Object) int {
return -1
}
func (cf *CompiledFunction) String() string { func (cf *CompiledFunction) String() string {
return cf.Inspect() return cf.Inspect()
} }
type Closure struct { type Closure struct {
BaseObject
Fn *CompiledFunction Fn *CompiledFunction
Free []Object Free []Object
} }

View File

@@ -23,6 +23,23 @@ func (e Error) Copy() Object {
return Error{Message: e.Message} return Error{Message: e.Message}
} }
func (e Error) Compare(other Object) int {
if obj, ok := other.(Error); ok {
if e.Message > obj.Message {
return 1
}
if e.Message == obj.Message {
return 0
}
return -1
}
return -1
}
func (e Error) Error() string {
return e.Message
}
func (e Error) String() string { func (e Error) String() string {
return e.Message return e.Message
} }

View File

@@ -7,6 +7,8 @@ import (
) )
type Function struct { type Function struct {
BaseObject
Parameters []*ast.Identifier Parameters []*ast.Identifier
Body *ast.BlockStatement Body *ast.BlockStatement
Env *Environment Env *Environment
@@ -43,6 +45,7 @@ func (f Function) String() string {
} }
type ReturnValue struct { type ReturnValue struct {
BaseObject
Value Object Value Object
} }

View File

@@ -97,18 +97,14 @@ func (h *Hash) Compare(other Object) int {
return -1 return -1
} }
for _, pair := range h.Pairs { for _, pair := range h.Pairs {
left := pair.Value hashed := pair.Value.(Hasher)
hashed := left.(Hasher)
right, ok := obj.Pairs[hashed.Hash()] right, ok := obj.Pairs[hashed.Hash()]
if !ok { if !ok {
return -1 return -1
} }
cmp, ok := left.(Comparable) val := pair.Value.Compare(right.Value)
if !ok { if val != 0 {
return -1 return val
}
if cmp.Compare(right.Value) != 0 {
return cmp.Compare(right.Value)
} }
} }

View File

@@ -57,11 +57,6 @@ func (t Type) String() string {
} }
} }
// Comparable is the interface for objects to implement suitable comparisons
type Comparable interface {
Compare(other Object) int
}
// Copyable is the interface for creating copies of objects // Copyable is the interface for creating copies of objects
type Copyable interface { type Copyable interface {
Copy() Object Copy() Object
@@ -73,9 +68,14 @@ type Object interface {
fmt.Stringer fmt.Stringer
Type() Type Type() Type
Bool() bool Bool() bool
Compare(Object) int
Inspect() string Inspect() string
} }
type BaseObject struct{}
func (o BaseObject) Compare(other Object) int { return -1 }
// Hasher is the interface for objects to provide suitable hash keys // Hasher is the interface for objects to provide suitable hash keys
type Hasher interface { type Hasher interface {
Hash() HashKey Hash() HashKey

View File

@@ -1,6 +1,8 @@
package server package server
import ( import (
"fmt"
"io"
"monkey" "monkey"
"net/http" "net/http"
@@ -12,6 +14,9 @@ func (s *Server) runHandler() router.Handle {
opts := &monkey.Options{ opts := &monkey.Options{
Stdout: w, Stdout: w,
Stderr: w, Stderr: w,
Exit: func(status int) {
io.WriteString(w, fmt.Sprintf("<p><i>Program exited with %d.</i></p>", status))
},
} }
err := monkey.ExecString(r.FormValue("code"), opts) err := monkey.ExecString(r.FormValue("code"), opts)

View File

@@ -9,7 +9,7 @@
<body> <body>
<div class="layout"> <div class="layout">
<div class="top"> <div class="top">
<b><a href="https://git.unflavoredmeson.com/unflavoredmeson/monkey-lango" <b><a href="https://gitea.unflavoredmeson.com/unflavoredmeson/monkey-lango"
style="color: #FFF; text-decoration: none;">Monkey</a></b> style="color: #FFF; text-decoration: none;">Monkey</a></b>
<span>⌘/Ctrl + ENTER to Run. ⌘/Ctrl + SPACE to Format.</span> <span>⌘/Ctrl + ENTER to Run. ⌘/Ctrl + SPACE to Format.</span>
</div> </div>

View File

@@ -6,9 +6,9 @@ import (
) )
type frame struct { type frame struct {
cl *object.Closure ip uint16
ip int
basePointer int basePointer int
cl *object.Closure
} }
func newFrame(cl *object.Closure, basePointer int) frame { func newFrame(cl *object.Closure, basePointer int) frame {
@@ -30,7 +30,7 @@ func (f *frame) SetFree(idx uint8, obj object.Object) {
f.cl.Free[idx] = obj f.cl.Free[idx] = obj
} }
func (f *frame) SetIP(ip int) { func (f *frame) SetIP(ip uint16) {
f.ip = ip f.ip = ip
} }

View File

@@ -2,7 +2,6 @@ package vm
import ( import (
"fmt" "fmt"
"log"
"monkey/internal/builtins" "monkey/internal/builtins"
"monkey/internal/code" "monkey/internal/code"
"monkey/internal/compiler" "monkey/internal/compiler"
@@ -13,8 +12,6 @@ import (
"monkey/internal/utils" "monkey/internal/utils"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"time"
"unicode" "unicode"
) )
@@ -151,20 +148,25 @@ func (vm *VM) popFrame() frame {
return vm.frames[vm.fp] return vm.frames[vm.fp]
} }
// Option defines a function option for the virtual machine.
type Option func(*VM) type Option func(*VM)
// WithContext defines an option to set the context for the virtual machine.
func WithContext(ctx context.Context) Option { func WithContext(ctx context.Context) Option {
return func(vm *VM) { vm.ctx = ctx } return func(vm *VM) { vm.ctx = ctx }
} }
// WithDebug enables debug mode in the VM.
func WithDebug(debug bool) Option { func WithDebug(debug bool) Option {
return func(vm *VM) { vm.debug = debug } return func(vm *VM) { vm.debug = debug }
} }
// WithState sets the state of the VM.
func WithState(state *State) Option { func WithState(state *State) Option {
return func(vm *VM) { vm.state = state } return func(vm *VM) { vm.state = state }
} }
// WithTrace sets the trace flag for the VM.
func WithTrace(trace bool) Option { func WithTrace(trace bool) Option {
return func(vm *VM) { vm.trace = trace } return func(vm *VM) { vm.trace = trace }
} }
@@ -224,6 +226,29 @@ func (vm *VM) pop() object.Object {
return o return o
} }
func (vm *VM) pop2() (object.Object, object.Object) {
if vm.sp == 1 {
panic("stack underflow")
}
o1 := vm.stack[vm.sp-1]
o2 := vm.stack[vm.sp-2]
vm.sp -= 2
return o1, o2
}
func (vm *VM) pop3() (object.Object, object.Object, object.Object) {
if vm.sp == 2 {
panic("stack underflow")
}
o1 := vm.stack[vm.sp-1]
o2 := vm.stack[vm.sp-2]
o3 := vm.stack[vm.sp-3]
vm.sp -= 3
return o1, o2, o3
}
func (vm *VM) executeSetGlobal() error { func (vm *VM) executeSetGlobal() error {
globalIndex := vm.currentFrame().ReadUint16() globalIndex := vm.currentFrame().ReadUint16()
@@ -301,8 +326,7 @@ func (vm *VM) executeMakeArray() error {
} }
func (vm *VM) executeAdd() error { func (vm *VM) executeAdd() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -335,8 +359,7 @@ func (vm *VM) executeAdd() error {
} }
func (vm *VM) executeSub() error { func (vm *VM) executeSub() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -351,8 +374,7 @@ func (vm *VM) executeSub() error {
} }
func (vm *VM) executeMul() error { func (vm *VM) executeMul() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case *object.Array: case *object.Array:
@@ -379,8 +401,7 @@ func (vm *VM) executeMul() error {
} }
func (vm *VM) executeDiv() error { func (vm *VM) executeDiv() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -396,8 +417,7 @@ func (vm *VM) executeDiv() error {
} }
func (vm *VM) executeMod() error { func (vm *VM) executeMod() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -412,8 +432,7 @@ func (vm *VM) executeMod() error {
} }
func (vm *VM) executeOr() error { func (vm *VM) executeOr() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Boolean: case object.Boolean:
@@ -428,8 +447,7 @@ func (vm *VM) executeOr() error {
} }
func (vm *VM) executeAnd() error { func (vm *VM) executeAnd() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Boolean: case object.Boolean:
@@ -444,8 +462,7 @@ func (vm *VM) executeAnd() error {
} }
func (vm *VM) executeBitwiseOr() error { func (vm *VM) executeBitwiseOr() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -460,8 +477,7 @@ func (vm *VM) executeBitwiseOr() error {
} }
func (vm *VM) executeBitwiseXor() error { func (vm *VM) executeBitwiseXor() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -476,8 +492,7 @@ func (vm *VM) executeBitwiseXor() error {
} }
func (vm *VM) executeBitwiseAnd() error { func (vm *VM) executeBitwiseAnd() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -503,8 +518,7 @@ func (vm *VM) executeBitwiseNot() error {
} }
func (vm *VM) executeLeftShift() error { func (vm *VM) executeLeftShift() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -519,8 +533,7 @@ func (vm *VM) executeLeftShift() error {
} }
func (vm *VM) executeRightShift() error { func (vm *VM) executeRightShift() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.Integer: case object.Integer:
@@ -535,63 +548,39 @@ func (vm *VM) executeRightShift() error {
} }
func (vm *VM) executeEqual() error { func (vm *VM) executeEqual() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
if obj, ok := left.(object.Comparable); ok { if left.Compare(right) == 0 {
val := obj.Compare(right) return vm.push(object.TRUE)
if val == 0 {
return vm.push(object.TRUE)
}
return vm.push(object.FALSE)
} }
return vm.push(object.FALSE)
return object.NewBinaryOpError(left, right, "==")
} }
func (vm *VM) executeNotEqual() error { func (vm *VM) executeNotEqual() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
if obj, ok := left.(object.Comparable); ok { if left.Compare(right) != 0 {
val := obj.Compare(right) return vm.push(object.TRUE)
if val != 0 {
return vm.push(object.TRUE)
}
return vm.push(object.FALSE)
} }
return vm.push(object.FALSE)
return object.NewBinaryOpError(left, right, "!=")
} }
func (vm *VM) executeGreaterThan() error { func (vm *VM) executeGreaterThan() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
if obj, ok := left.(object.Comparable); ok { if left.Compare(right) == 1 {
val := obj.Compare(right) return vm.push(object.TRUE)
if val == 1 {
return vm.push(object.TRUE)
}
return vm.push(object.FALSE)
} }
return vm.push(object.FALSE)
return object.NewBinaryOpError(left, right, ">")
} }
func (vm *VM) executeGreaterThanOrEqual() error { func (vm *VM) executeGreaterThanOrEqual() error {
right := vm.pop() right, left := vm.pop2()
left := vm.pop()
if obj, ok := left.(object.Comparable); ok { if left.Compare(right) == 1 {
val := obj.Compare(right) return vm.push(object.TRUE)
if val >= 0 {
return vm.push(object.TRUE)
}
return vm.push(object.FALSE)
} }
return vm.push(object.FALSE)
return object.NewBinaryOpError(left, right, ">")
} }
func (vm *VM) executeNot() error { func (vm *VM) executeNot() error {
@@ -621,9 +610,7 @@ func (vm *VM) executeMinus() error {
} }
func (vm *VM) executeSetItem() error { func (vm *VM) executeSetItem() error {
right := vm.pop() right, index, left := vm.pop3()
index := vm.pop()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case *object.Array: case *object.Array:
@@ -647,8 +634,7 @@ func (vm *VM) executeSetItem() error {
} }
func (vm *VM) executeGetItem() error { func (vm *VM) executeGetItem() error {
index := vm.pop() index, left := vm.pop2()
left := vm.pop()
switch obj := left.(type) { switch obj := left.(type) {
case object.String: case object.String:
@@ -749,7 +735,7 @@ func (vm *VM) pushClosure(constIndex, numFree int) error {
for i := 0; i < numFree; i++ { for i := 0; i < numFree; i++ {
free[i] = vm.stack[vm.sp-numFree+i] free[i] = vm.stack[vm.sp-numFree+i]
} }
vm.sp = vm.sp - numFree vm.sp -= numFree
closure := object.Closure{Fn: function, Free: free} closure := object.Closure{Fn: function, Free: free}
return vm.push(&closure) return vm.push(&closure)
@@ -778,49 +764,9 @@ func (vm *VM) LastPoppedStackElem() object.Object {
} }
func (vm *VM) Run() (err error) { func (vm *VM) Run() (err error) {
var (
op code.Opcode
opcodeFreqs = make(map[code.Opcode]int)
)
if vm.debug {
start := time.Now()
defer func() {
end := time.Now().Sub(start)
total := 0
opcodes := make([]code.Opcode, 0, len(opcodeFreqs))
for opcode, count := range opcodeFreqs {
opcodes = append(opcodes, opcode)
total += count
}
sort.SliceStable(opcodes, func(i, j int) bool {
return opcodeFreqs[opcodes[i]] > opcodeFreqs[opcodes[j]]
})
log.Printf("%d instructions executed in %s %d/µs", total, end, total/int(end.Microseconds()))
log.Print("Top Instructions:")
for _, opcode := range opcodes[:min(len(opcodes), 10)] {
log.Printf("%10d %s", opcodeFreqs[opcode], opcode)
}
}()
}
for err == nil { for err == nil {
op = vm.currentFrame().ReadNextOp() op := vm.currentFrame().ReadNextOp()
if vm.debug {
opcodeFreqs[op]++
log.Printf(
"%-25s %-20s\n",
fmt.Sprintf("%04d %s", vm.currentFrame().ip, op),
fmt.Sprintf(
"[ip=%02d fp=%02d, sp=%02d]",
vm.currentFrame().ip, vm.fp-1, vm.sp,
),
)
}
switch op { switch op {
case code.OpConstant: case code.OpConstant:
@@ -837,11 +783,11 @@ func (vm *VM) Run() (err error) {
err = vm.push(object.FALSE) err = vm.push(object.FALSE)
case code.OpJump: case code.OpJump:
pos := int(vm.currentFrame().ReadUint16()) pos := vm.currentFrame().ReadUint16()
vm.currentFrame().SetIP(pos) vm.currentFrame().SetIP(pos)
case code.OpJumpNotTruthy: case code.OpJumpNotTruthy:
pos := int(vm.currentFrame().ReadUint16()) pos := vm.currentFrame().ReadUint16()
if !isTruthy(vm.pop()) { if !isTruthy(vm.pop()) {
vm.currentFrame().SetIP(pos) vm.currentFrame().SetIP(pos)
} }
@@ -979,14 +925,7 @@ func (vm *VM) Run() (err error) {
default: default:
err = fmt.Errorf("unhandled opcode: %s", op) err = fmt.Errorf("unhandled opcode: %s", op)
} }
if vm.trace {
log.Printf(
"%-25s [ip=%02d fp=%02d, sp=%02d]",
"", vm.currentFrame().ip, vm.fp-1, vm.sp,
)
}
} }
return err return
} }

View File

@@ -713,12 +713,10 @@ func TestBuiltinFunctions(t *testing.T) {
{`len("hello world")`, 11}, {`len("hello world")`, 11},
{ {
`len(1)`, `len(1)`,
&object.Error{ fmt.Errorf("TypeError: object of type 'int' has no len()"),
Message: "TypeError: object of type 'int' has no len()",
},
}, },
{`len("one", "two")`, {`len("one", "two")`,
&object.Error{ object.Error{
Message: "TypeError: len() takes exactly 1 argument (2 given)", Message: "TypeError: len() takes exactly 1 argument (2 given)",
}, },
}, },
@@ -728,14 +726,14 @@ func TestBuiltinFunctions(t *testing.T) {
{`first([1, 2, 3])`, 1}, {`first([1, 2, 3])`, 1},
{`first([])`, object.NULL}, {`first([])`, object.NULL},
{`first(1)`, {`first(1)`,
&object.Error{ object.Error{
Message: "TypeError: first() expected argument #1 to be `array` got `int`", Message: "TypeError: first() expected argument #1 to be `array` got `int`",
}, },
}, },
{`last([1, 2, 3])`, 3}, {`last([1, 2, 3])`, 3},
{`last([])`, object.NULL}, {`last([])`, object.NULL},
{`last(1)`, {`last(1)`,
&object.Error{ object.Error{
Message: "TypeError: last() expected argument #1 to be `array` got `int`", Message: "TypeError: last() expected argument #1 to be `array` got `int`",
}, },
}, },
@@ -743,12 +741,12 @@ func TestBuiltinFunctions(t *testing.T) {
{`rest([])`, []int{}}, {`rest([])`, []int{}},
{`push([], 1)`, []int{1}}, {`push([], 1)`, []int{1}},
{`push(1, 1)`, {`push(1, 1)`,
&object.Error{ object.Error{
Message: "TypeError: push() expected argument #1 to be `array` got `int`", Message: "TypeError: push() expected argument #1 to be `array` got `int`",
}, },
}, },
{`input()`, ""}, {`input()`, ""},
{`pop([])`, &object.Error{ {`pop([])`, object.Error{
Message: "IndexError: pop from an empty array", Message: "IndexError: pop from an empty array",
}, },
}, },

View File

@@ -99,6 +99,10 @@ func ExecFile(fn string, opts *Options) error {
err error err error
) )
if opts == nil {
opts = &Options{}
}
ext := filepath.Ext(fn) ext := filepath.Ext(fn)
mc := fn[:len(fn)-len(ext)] + ".mc" mc := fn[:len(fn)-len(ext)] + ".mc"
@@ -128,7 +132,7 @@ func ExecFile(fn string, opts *Options) error {
} }
} }
mvm := vm.New(fn, bytecode, vm.WithDebug(opts.Debug), vm.WithTrace(opts.Trace)) mvm := vm.New(fn, bytecode, opts.ToVMOptions()...)
if err := mvm.Run(); err != nil { if err := mvm.Run(); err != nil {
return err return err
} }
@@ -138,6 +142,10 @@ func ExecFile(fn string, opts *Options) error {
// ExecString executes a Monkey program from a string. // ExecString executes a Monkey program from a string.
func ExecString(input string, opts *Options) error { func ExecString(input string, opts *Options) error {
if opts == nil {
opts = &Options{}
}
bytecode, err := compileString(input, opts.Debug) bytecode, err := compileString(input, opts.Debug)
if err != nil { if err != nil {
return err return err
@@ -157,6 +165,10 @@ func ExecString(input string, opts *Options) error {
// CompileFiles compiles multiple Monkey files and returns any errors encountered during compilation. // CompileFiles compiles multiple Monkey files and returns any errors encountered during compilation.
func CompileFiles(fns []string, opts *Options) error { func CompileFiles(fns []string, opts *Options) error {
if opts == nil {
opts = &Options{}
}
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"

View File

@@ -1,23 +1,38 @@
package monkey package monkey
import ( import (
"context"
"io" "io"
"monkey/internal/context"
"monkey/internal/vm" "monkey/internal/vm"
"os"
) )
// Options represents the options for running a script with Monkey.
type Options struct { type Options struct {
Debug bool Debug bool // Whether to enable debug mode or not
Trace bool Trace bool // Whether to enable trace mode or not
Args []string Args []string // The arguments passed to the script
Stdin io.Reader Stdin io.Reader // The input reader for the script
Stdout io.Writer Stdout io.Writer // The output writer for the script
Stderr io.Writer Stderr io.Writer // The error writer for the script
Exit func(int) Exit func(int) // A function to call when the script exits
} }
func (opts Options) ToVMOptions() []vm.Option { func (opts Options) ToVMOptions() []vm.Option {
if opts.Stdin == nil {
opts.Stdin = os.Stdin
}
if opts.Stdout == nil {
opts.Stdout = os.Stdout
}
if opts.Stderr == nil {
opts.Stderr = os.Stderr
}
if opts.Exit == nil {
opts.Exit = os.Exit
}
ctx := context.New( ctx := context.New(
context.WithArgs(opts.Args), context.WithArgs(opts.Args),
context.WithStdin(opts.Stdin), context.WithStdin(opts.Stdin),
@@ -28,7 +43,7 @@ func (opts Options) ToVMOptions() []vm.Option {
return []vm.Option{ return []vm.Option{
vm.WithDebug(opts.Debug), vm.WithDebug(opts.Debug),
vm.WithDebug(opts.Trace), vm.WithTrace(opts.Trace),
vm.WithContext(ctx), vm.WithContext(ctx),
} }
} }

46
repl.go
View File

@@ -6,7 +6,6 @@ import (
"github.com/tebeka/atexit" "github.com/tebeka/atexit"
"io" "io"
"log" "log"
"monkey/internal/context"
"os" "os"
"strings" "strings"
@@ -22,7 +21,10 @@ 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, opts *Options) error { func REPL(opts *Options) error {
if opts == nil {
opts = &Options{}
}
initState, err := term.MakeRaw(0) initState, err := term.MakeRaw(0)
if err != nil { if err != nil {
@@ -42,12 +44,7 @@ func REPL(args []string, opts *Options) error {
t := term.NewTerminal(os.Stdin, ">>> ") t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete t.AutoCompleteCallback = autoComplete
ctx := context.New( opts.Exit = atexit.Exit
context.WithArgs(args),
context.WithStdout(t),
context.WithStderr(t),
context.WithExit(atexit.Exit),
)
state := vm.NewState() state := vm.NewState()
@@ -86,14 +83,7 @@ func REPL(args []string, opts *Options) error {
log.Printf("Bytecode:\n%s\n", c.Bytecode()) log.Printf("Bytecode:\n%s\n", c.Bytecode())
} }
opts := []vm.Option{ mvm := vm.New("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
vm.WithContext(ctx),
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)
@@ -152,8 +142,12 @@ func acceptUntil(t *term.Terminal, start, end string) (string, error) {
} }
// SimpleREPL provides a simple read-eval-print loop for the monkey virtual machine. // SimpleREPL provides a simple read-eval-print loop for the monkey virtual machine.
func SimpleREPL(args []string, opts *Options) { func SimpleREPL(opts *Options) {
var reader = bufio.NewReader(os.Stdin) if opts == nil {
opts = &Options{}
}
reader := bufio.NewReader(os.Stdin)
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
@@ -164,13 +158,6 @@ func SimpleREPL(args []string, opts *Options) {
t := term.NewTerminal(os.Stdin, ">>> ") t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete t.AutoCompleteCallback = autoComplete
ctx := context.New(
context.WithArgs(args),
context.WithStdout(t),
context.WithStderr(t),
context.WithExit(atexit.Exit),
)
state := vm.NewState() state := vm.NewState()
PrintVersionInfo(os.Stdout) PrintVersionInfo(os.Stdout)
@@ -209,14 +196,7 @@ func SimpleREPL(args []string, opts *Options) {
log.Printf("Bytecode:\n%s\n", c.Bytecode()) log.Printf("Bytecode:\n%s\n", c.Bytecode())
} }
opts := []vm.Option{ mvm := vm.New("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
vm.WithContext(ctx),
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)