optimizations
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
33
.gitea/workflows/publish.yml
Normal file
33
.gitea/workflows/publish.yml
Normal 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
15
.idea/git_toolbox_prj.xml
generated
Normal 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>
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
if val == 0 {
|
|
||||||
return vm.push(object.TRUE)
|
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)
|
|
||||||
if val != 0 {
|
|
||||||
return vm.push(object.TRUE)
|
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)
|
|
||||||
if val == 1 {
|
|
||||||
return vm.push(object.TRUE)
|
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)
|
|
||||||
if val >= 0 {
|
|
||||||
return vm.push(object.TRUE)
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
14
monkey.go
14
monkey.go
@@ -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"
|
||||||
|
|||||||
33
options.go
33
options.go
@@ -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
46
repl.go
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user