restructure project
Some checks failed
Build / build (push) Failing after 5m21s
Publish Image / publish (push) Failing after 32s
Test / build (push) Failing after 5m8s

This commit is contained in:
Chuck Smith
2024-03-28 16:20:09 -04:00
parent 362138ff2e
commit fc6ceee02c
93 changed files with 479 additions and 194 deletions

452
internal/ast/ast.go Normal file
View File

@@ -0,0 +1,452 @@
package ast
import (
"bytes"
"fmt"
"monkey/internal/token"
"strings"
)
// The base Node interface
type Node interface {
TokenLiteral() string
String() string
}
// All statement nodes implement this
type Statement interface {
Node
statementNode()
}
// All expression nodes implement this
type Expression interface {
Node
expressionNode()
}
type Program struct {
Statements []Statement
}
func (p *Program) TokenLiteral() string {
if len(p.Statements) > 0 {
return p.Statements[0].TokenLiteral()
} else {
return ""
}
}
func (p *Program) String() string {
var out bytes.Buffer
for _, s := range p.Statements {
out.WriteString(s.String())
}
return out.String()
}
type ReturnStatement struct {
Token token.Token // the 'return' token
ReturnValue Expression
}
func (rs *ReturnStatement) statementNode() {}
func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal }
func (rs *ReturnStatement) String() string {
var out bytes.Buffer
out.WriteString(rs.TokenLiteral() + " ")
if rs.ReturnValue != nil {
out.WriteString(rs.ReturnValue.String())
}
out.WriteString(";")
return out.String()
}
type ExpressionStatement struct {
Token token.Token // the first token of the expression
Expression Expression
}
func (es *ExpressionStatement) statementNode() {}
func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
func (es *ExpressionStatement) String() string {
if es.Expression != nil {
return es.Expression.String()
}
return ""
}
type BlockStatement struct {
Token token.Token // the { token
Statements []Statement
}
func (bs *BlockStatement) statementNode() {}
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal }
func (bs *BlockStatement) String() string {
var out bytes.Buffer
for _, s := range bs.Statements {
out.WriteString(s.String())
}
return out.String()
}
// Expressions
type Identifier struct {
Token token.Token // the token.IDENT token
Value string
}
func (i *Identifier) expressionNode() {}
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
func (i *Identifier) String() string { return i.Value }
// Null represents a null value
type Null struct {
Token token.Token
}
func (n *Null) expressionNode() {}
func (n *Null) TokenLiteral() string { return n.Token.Literal }
func (n *Null) String() string { return n.Token.Literal }
type Boolean struct {
Token token.Token
Value bool
}
func (b *Boolean) expressionNode() {}
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
func (b *Boolean) String() string { return b.Token.Literal }
type IntegerLiteral struct {
Token token.Token
Value int64
}
func (il *IntegerLiteral) expressionNode() {}
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
func (il *IntegerLiteral) String() string { return il.Token.Literal }
type PrefixExpression struct {
Token token.Token // The prefix token, e.g. !
Operator string
Right Expression
}
func (pe *PrefixExpression) expressionNode() {}
func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
func (pe *PrefixExpression) String() string {
var out bytes.Buffer
out.WriteString("(")
out.WriteString(pe.Operator)
out.WriteString(pe.Right.String())
out.WriteString(")")
return out.String()
}
type InfixExpression struct {
Token token.Token // The operator token, e.g. +
Left Expression
Operator string
Right Expression
}
func (ie *InfixExpression) expressionNode() {}
func (ie *InfixExpression) TokenLiteral() string { return ie.Token.Literal }
func (ie *InfixExpression) String() string {
var out bytes.Buffer
out.WriteString("(")
out.WriteString(ie.Left.String())
out.WriteString(" " + ie.Operator + " ")
out.WriteString(ie.Right.String())
out.WriteString(")")
return out.String()
}
type IfExpression struct {
Token token.Token // The 'if' token
Condition Expression
Consequence *BlockStatement
Alternative *BlockStatement
}
func (ie *IfExpression) expressionNode() {}
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
func (ie *IfExpression) String() string {
var out bytes.Buffer
out.WriteString("if")
out.WriteString(ie.Condition.String())
out.WriteString(" ")
out.WriteString(ie.Consequence.String())
if ie.Alternative != nil {
out.WriteString("else ")
out.WriteString(ie.Alternative.String())
}
return out.String()
}
type FunctionLiteral struct {
Token token.Token // The 'fn' token
Parameters []*Identifier
Body *BlockStatement
Name string
}
func (fl *FunctionLiteral) expressionNode() {}
func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal }
func (fl *FunctionLiteral) String() string {
var out bytes.Buffer
params := []string{}
for _, p := range fl.Parameters {
params = append(params, p.String())
}
out.WriteString(fl.TokenLiteral())
if fl.Name != "" {
out.WriteString(fmt.Sprintf("<%s>", fl.Name))
}
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(") ")
out.WriteString(fl.Body.String())
return out.String()
}
type CallExpression struct {
Token token.Token // The '(' token
Function Expression // Identifier or FunctionLiteral
Arguments []Expression
}
func (ce *CallExpression) expressionNode() {}
func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
func (ce *CallExpression) String() string {
var out bytes.Buffer
args := []string{}
for _, a := range ce.Arguments {
args = append(args, a.String())
}
out.WriteString(ce.Function.String())
out.WriteString("(")
out.WriteString(strings.Join(args, ", "))
out.WriteString(")")
return out.String()
}
type StringLiteral struct {
Token token.Token
Value string
}
func (sl *StringLiteral) TokenLiteral() string {
return sl.Token.Literal
}
func (sl *StringLiteral) String() string {
return sl.Token.Literal
}
func (sl *StringLiteral) expressionNode() {}
type ArrayLiteral struct {
Token token.Token // the '[' token
Elements []Expression
}
func (al *ArrayLiteral) TokenLiteral() string {
return al.Token.Literal
}
func (al *ArrayLiteral) String() string {
var out bytes.Buffer
elements := []string{}
for _, el := range al.Elements {
elements = append(elements, el.String())
}
out.WriteString("[")
out.WriteString(strings.Join(elements, ", "))
out.WriteString("]")
return out.String()
}
func (al *ArrayLiteral) expressionNode() {}
type IndexExpression struct {
Token token.Token // The [ token
Left Expression
Index Expression
}
func (ie *IndexExpression) TokenLiteral() string {
return ie.Token.Literal
}
func (ie *IndexExpression) String() string {
var out bytes.Buffer
out.WriteString("(")
out.WriteString(ie.Left.String())
out.WriteString("[")
out.WriteString(ie.Index.String())
out.WriteString("])")
return out.String()
}
func (ie *IndexExpression) expressionNode() {}
type HashLiteral struct {
Token token.Token // the '{' token
Pairs map[Expression]Expression
}
func (hl *HashLiteral) TokenLiteral() string {
return hl.Token.Literal
}
func (hl *HashLiteral) String() string {
var out bytes.Buffer
pairs := []string{}
for key, value := range hl.Pairs {
pairs = append(pairs, key.String()+":"+value.String())
}
out.WriteString("{")
out.WriteString(strings.Join(pairs, ", "))
out.WriteString("}")
return out.String()
}
func (hl *HashLiteral) expressionNode() {}
type WhileExpression struct {
Token token.Token // The 'while' token
Condition Expression
Consequence *BlockStatement
}
func (we *WhileExpression) expressionNode() {}
// TokenLiteral prints the literal value of the token associated with this node
func (we *WhileExpression) TokenLiteral() string { return we.Token.Literal }
// String returns a stringified version of the AST for debugging
func (we *WhileExpression) String() string {
var out bytes.Buffer
out.WriteString("while")
out.WriteString(we.Condition.String())
out.WriteString(" ")
out.WriteString(we.Consequence.String())
return out.String()
}
// BindExpression represents an assignment expression of the form:
// x := 1
type BindExpression struct {
Token token.Token // the := token
Left Expression
Value Expression
}
func (be *BindExpression) TokenLiteral() string {
return be.Token.Literal
}
func (be *BindExpression) String() string {
var out bytes.Buffer
out.WriteString(be.Left.String())
out.WriteString(be.TokenLiteral())
out.WriteString(be.Value.String())
return out.String()
}
func (be *BindExpression) expressionNode() {}
// AssignmentExpression represents an assignment expression of the form:
// x = 1 or xs[1] = 2
type AssignmentExpression struct {
Token token.Token // the token.ASSIGN token
Left Expression
Value Expression
}
func (as *AssignmentExpression) TokenLiteral() string {
return as.Token.Literal
}
func (as *AssignmentExpression) String() string {
var out bytes.Buffer
out.WriteString(as.Left.String())
out.WriteString(as.TokenLiteral())
out.WriteString(as.Value.String())
return out.String()
}
func (as *AssignmentExpression) expressionNode() {}
// Comment a comment
type Comment struct {
Token token.Token // the token.COMMENT token
Value string
}
func (c *Comment) statementNode() {}
// TokenLiteral prints the literal value of the token associated with this node
func (c *Comment) TokenLiteral() string { return c.Token.Literal }
// String returns a stringified version of the AST for debugging
func (c *Comment) String() string {
var out bytes.Buffer
out.WriteString(c.TokenLiteral() + " ")
out.WriteString(c.Value)
return out.String()
}
// ImportExpression represents an `import` expression and holds the name
// of the module being imported.
type ImportExpression struct {
Token token.Token // The 'import' token
Name Expression
}
func (ie *ImportExpression) TokenLiteral() string {
return ie.Token.Literal
}
func (ie *ImportExpression) String() string {
var out bytes.Buffer
out.WriteString(ie.TokenLiteral())
out.WriteString("(")
out.WriteString(fmt.Sprintf("\"%s\"", ie.Name))
out.WriteString(")")
return out.String()
}
func (ie *ImportExpression) expressionNode() {}

30
internal/ast/ast_test.go Normal file
View File

@@ -0,0 +1,30 @@
package ast
import (
"github.com/stretchr/testify/assert"
"monkey/internal/token"
"testing"
)
func TestString(t *testing.T) {
program := &Program{
Statements: []Statement{
&ExpressionStatement{
Token: token.Token{Type: token.IDENT, Literal: "myVar"},
Expression: &BindExpression{
Token: token.Token{Type: token.BIND, Literal: ":="},
Left: &Identifier{
Token: token.Token{Type: token.IDENT, Literal: "myVar"},
Value: "myVar",
},
Value: &Identifier{
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
Value: "anotherVar",
},
},
},
},
}
assert.Equal(t, "myVar:=anotherVar", program.String())
}

24
internal/builtins/abs.go Normal file
View File

@@ -0,0 +1,24 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Abs ...
func Abs(args ...object.Object) object.Object {
if err := typing.Check(
"abs", args,
typing.ExactArgs(1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
i := args[0].(*object.Integer)
value := i.Value
if value < 0 {
value = value * -1
}
return &object.Integer{Value: value}
}

View File

@@ -0,0 +1,32 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// Accept ...
func Accept(args ...object.Object) object.Object {
if err := typing.Check(
"accept", args,
typing.ExactArgs(1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
var (
nfd syscall.Handle
err error
)
fd := int(args[0].(*object.Integer).Value)
nfd, _, err = syscall.Accept(syscall.Handle(fd))
if err != nil {
return newError("SocketError: %s", err)
}
return &object.Integer{Value: int64(nfd)}
}

22
internal/builtins/args.go Normal file
View File

@@ -0,0 +1,22 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Args ...
func Args(args ...object.Object) object.Object {
if err := typing.Check(
"args", args,
typing.ExactArgs(0),
); err != nil {
return newError(err.Error())
}
elements := make([]object.Object, len(object.Arguments))
for i, arg := range object.Arguments {
elements[i] = &object.String{Value: arg}
}
return &object.Array{Elements: elements}
}

View File

@@ -0,0 +1,26 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"monkey/internal/typing"
"os"
)
// Assert ...
func Assert(args ...object.Object) object.Object {
if err := typing.Check(
"assert", args,
typing.ExactArgs(2),
typing.WithTypes(object.BOOLEAN_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
if !args[0].(*object.Boolean).Value {
fmt.Printf("Assertion Error: %s", args[1].(*object.String).Value)
os.Exit(1)
}
return nil
}

22
internal/builtins/bin.go Normal file
View File

@@ -0,0 +1,22 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"monkey/internal/typing"
"strconv"
)
// Bin ...
func Bin(args ...object.Object) object.Object {
if err := typing.Check(
"bin", args,
typing.ExactArgs(1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
i := args[0].(*object.Integer)
return &object.String{Value: fmt.Sprintf("0b%s", strconv.FormatInt(i.Value, 2))}
}

54
internal/builtins/bind.go Normal file
View File

@@ -0,0 +1,54 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// Bind ...
func Bind(args ...object.Object) object.Object {
if err := typing.Check(
"bind", args,
typing.ExactArgs(2),
typing.WithTypes(object.INTEGER_OBJ, object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
var (
err error
sockaddr syscall.Sockaddr
)
fd := int(args[0].(*object.Integer).Value)
address := args[1].(*object.String).Value
sockaddr, err = syscall.Getsockname(syscall.Handle(fd))
if err != nil {
return newError("ValueError: %s", err)
}
if _, ok := sockaddr.(*syscall.SockaddrInet4); ok {
addr, port, err := parseV4Address(address)
if err != nil {
return newError("ValueError: Invalid IPv4 address '%s': %s", address, err)
}
sockaddr = &syscall.SockaddrInet4{Addr: addr, Port: port}
} else if _, ok := sockaddr.(*syscall.SockaddrInet6); ok {
addr, port, err := parseV6Address(address)
if err != nil {
return newError("ValueError: Invalid IPv6 address '%s': %s", address, err)
}
sockaddr = &syscall.SockaddrInet6{Addr: addr, Port: port}
} else {
return newError("ValueError: Invalid socket type %T for bind '%s'", sockaddr, address)
}
err = syscall.Bind(syscall.Handle(fd), sockaddr)
if err != nil {
return newError("SocketError: %s", err)
}
return &object.Null{}
}

18
internal/builtins/bool.go Normal file
View File

@@ -0,0 +1,18 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Bool ...
func Bool(args ...object.Object) object.Object {
if err := typing.Check(
"bool", args,
typing.ExactArgs(1),
); err != nil {
return newError(err.Error())
}
return &object.Boolean{Value: args[0].Bool()}
}

View File

@@ -0,0 +1,77 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"sort"
)
// Builtins ...
var Builtins = map[string]*object.Builtin{
"len": {Name: "len", Fn: Len},
"input": {Name: "input", Fn: Input},
"print": {Name: "print", Fn: Print},
"first": {Name: "first", Fn: First},
"last": {Name: "last", Fn: Last},
"rest": {Name: "rest", Fn: Rest},
"push": {Name: "push", Fn: Push},
"pop": {Name: "pop", Fn: Pop},
"exit": {Name: "exit", Fn: Exit},
"assert": {Name: "assert", Fn: Assert},
"bool": {Name: "bool", Fn: Bool},
"int": {Name: "int", Fn: Int},
"str": {Name: "str", Fn: Str},
"type": {Name: "type", Fn: TypeOf},
"args": {Name: "args", Fn: Args},
"lower": {Name: "lower", Fn: Lower},
"upper": {Name: "upper", Fn: Upper},
"join": {Name: "join", Fn: Join},
"split": {Name: "split", Fn: Split},
"find": {Name: "find", Fn: Find},
"readfile": {Name: "readfile", Fn: ReadFile},
"writefile": {Name: "writefile", Fn: WriteFile},
"ffi": {Name: "ffi", Fn: FFI},
"abs": {Name: "abs", Fn: Abs},
"bin": {Name: "bin", Fn: Bin},
"hex": {Name: "hex", Fn: Hex},
"ord": {Name: "ord", Fn: Ord},
"chr": {Name: "chr", Fn: Chr},
"divmod": {Name: "divmod", Fn: Divmod},
"hash": {Name: "hash", Fn: HashOf},
"id": {Name: "id", Fn: IdOf},
"oct": {Name: "oct", Fn: Oct},
"pow": {Name: "pow", Fn: Pow},
"min": {Name: "min", Fn: Min},
"max": {Name: "max", Fn: Max},
"sorted": {Name: "sorted", Fn: Sorted},
"reversed": {Name: "reversed", Fn: Reversed},
"open": {Name: "open", Fn: Open},
"close": {Name: "close", Fn: Close},
"write": {Name: "write", Fn: Write},
"read": {Name: "read", Fn: Read},
"seek": {Name: "seek", Fn: Seek},
"socket": {Name: "socket", Fn: Socket},
"bind": {Name: "bind", Fn: Bind},
"accept": {Name: "accept", Fn: Accept},
"listen": {Name: "listen", Fn: Listen},
"connect": {Name: "connect", Fn: Connect},
}
// BuiltinsIndex ...
var BuiltinsIndex []*object.Builtin
func init() {
var keys []string
for k := range Builtins {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
BuiltinsIndex = append(BuiltinsIndex, Builtins[k])
}
}
func newError(format string, a ...interface{}) *object.Error {
return &object.Error{Message: fmt.Sprintf(format, a...)}
}

21
internal/builtins/chr.go Normal file
View File

@@ -0,0 +1,21 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"monkey/internal/typing"
)
// Chr ...
func Chr(args ...object.Object) object.Object {
if err := typing.Check(
"chr", args,
typing.ExactArgs(1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
i := args[0].(*object.Integer)
return &object.String{Value: fmt.Sprintf("%c", rune(i.Value))}
}

View File

@@ -0,0 +1,27 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// Close ...
func Close(args ...object.Object) object.Object {
if err := typing.Check(
"close", args,
typing.ExactArgs(1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
fd := int(args[0].(*object.Integer).Value)
err := syscall.Close(syscall.Handle(fd))
if err != nil {
return newError("IOError: %s", err)
}
return &object.Null{}
}

View File

@@ -0,0 +1,50 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// Connect ...
func Connect(args ...object.Object) object.Object {
if err := typing.Check(
"connect", args,
typing.ExactArgs(2),
typing.WithTypes(object.INTEGER_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
var sa syscall.Sockaddr
fd := int(args[0].(*object.Integer).Value)
address := args[1].(*object.String).Value
sockaddr, err := syscall.Getsockname(syscall.Handle(fd))
if err != nil {
return newError("ValueError: %s", err)
}
if _, ok := sockaddr.(*syscall.SockaddrInet4); ok {
addr, port, err := parseV4Address(address)
if err != nil {
return newError("ValueError: Invalid IPv4 address '%s': %s", address, err)
}
sa = &syscall.SockaddrInet4{Addr: addr, Port: port}
} else if _, ok := sockaddr.(*syscall.SockaddrInet6); ok {
addr, port, err := parseV6Address(address)
if err != nil {
return newError("ValueError: Invalid IPv6 address '%s': %s", address, err)
}
sa = &syscall.SockaddrInet6{Addr: addr, Port: port}
} else {
return newError("ValueError: Invalid socket type %T for bind '%s'", sockaddr, address)
}
if err = syscall.Connect(syscall.Handle(fd), sa); err != nil {
return newError("SocketError: %s", err)
}
return &object.Null{}
}

View File

@@ -0,0 +1,24 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Divmod ...
func Divmod(args ...object.Object) object.Object {
if err := typing.Check(
"divmod", args,
typing.ExactArgs(2),
typing.WithTypes(object.INTEGER_OBJ, object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
a := args[0].(*object.Integer)
b := args[1].(*object.Integer)
elements := make([]object.Object, 2)
elements[0] = &object.Integer{Value: a.Value / b.Value}
elements[1] = &object.Integer{Value: a.Value % b.Value}
return &object.Array{Elements: elements}
}

26
internal/builtins/exit.go Normal file
View File

@@ -0,0 +1,26 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Exit ...
func Exit(args ...object.Object) object.Object {
if err := typing.Check(
"exit", args,
typing.RangeOfArgs(0, 1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
var status int
if len(args) == 1 {
status = int(args[0].(*object.Integer).Value)
}
object.ExitFunction(status)
return nil
}

37
internal/builtins/ffi.go Normal file
View File

@@ -0,0 +1,37 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
import (
"fmt"
"plugin"
)
// FFI ...
func FFI(args ...object.Object) object.Object {
if err := typing.Check(
"ffi", args,
typing.ExactArgs(2),
typing.WithTypes(object.STRING_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
name := args[0].(*object.String).Value
symbol := args[1].(*object.String).Value
p, err := plugin.Open(fmt.Sprintf("%s.so", name))
if err != nil {
return newError("error loading plugin: %s", err)
}
v, err := p.Lookup(symbol)
if err != nil {
return newError("error finding symbol: %s", err)
}
return &object.Builtin{Name: symbol, Fn: v.(object.BuiltinFunction)}
}

52
internal/builtins/find.go Normal file
View File

@@ -0,0 +1,52 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"sort"
)
import (
"strings"
)
// Find ...
func Find(args ...object.Object) object.Object {
if err := typing.Check(
"find", args,
typing.ExactArgs(2),
); err != nil {
return newError(err.Error())
}
// find substring in string
if haystack, ok := args[0].(*object.String); ok {
if err := typing.Check(
"find", args,
typing.WithTypes(object.STRING_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
needle := args[1].(*object.String)
index := strings.Index(haystack.Value, needle.Value)
return &object.Integer{Value: int64(index)}
}
// find in array
if haystack, ok := args[0].(*object.Array); ok {
needle := args[1].(object.Comparable)
i := sort.Search(len(haystack.Elements), func(i int) bool {
return needle.Compare(haystack.Elements[i]) == 0
})
if i < len(haystack.Elements) && needle.Compare(haystack.Elements[i]) == 0 {
return &object.Integer{Value: int64(i)}
}
return &object.Integer{Value: -1}
}
return newError(
"TypeError: find() expected argument #1 to be `array` or `str` got `%s`",
args[0].Type(),
)
}

View File

@@ -0,0 +1,24 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// First ...
func First(args ...object.Object) object.Object {
if err := typing.Check(
"first", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
if len(arr.Elements) > 0 {
return arr.Elements[0]
}
return nil
}

22
internal/builtins/hash.go Normal file
View File

@@ -0,0 +1,22 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// HashOf ...
func HashOf(args ...object.Object) object.Object {
if err := typing.Check(
"hash", args,
typing.ExactArgs(1),
); err != nil {
return newError(err.Error())
}
if hash, ok := args[0].(object.Hashable); ok {
return &object.Integer{Value: int64(hash.HashKey().Value)}
}
return newError("TypeError: hash() expected argument #1 to be hashable")
}

22
internal/builtins/hex.go Normal file
View File

@@ -0,0 +1,22 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"monkey/internal/typing"
"strconv"
)
// Hex ...
func Hex(args ...object.Object) object.Object {
if err := typing.Check(
"hex", args,
typing.ExactArgs(1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
i := args[0].(*object.Integer)
return &object.String{Value: fmt.Sprintf("0x%s", strconv.FormatInt(i.Value, 16))}
}

41
internal/builtins/id.go Normal file
View File

@@ -0,0 +1,41 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"monkey/internal/typing"
)
// IdOf ...
func IdOf(args ...object.Object) object.Object {
if err := typing.Check(
"id", args,
typing.ExactArgs(1),
); err != nil {
return newError(err.Error())
}
arg := args[0]
if n, ok := arg.(*object.Null); ok {
return &object.String{Value: fmt.Sprintf("%p", n)}
} else if b, ok := arg.(*object.Boolean); ok {
return &object.String{Value: fmt.Sprintf("%p", b)}
} else if i, ok := arg.(*object.Integer); ok {
return &object.String{Value: fmt.Sprintf("%p", i)}
} else if s, ok := arg.(*object.String); ok {
return &object.String{Value: fmt.Sprintf("%p", s)}
} else if a, ok := arg.(*object.Array); ok {
return &object.String{Value: fmt.Sprintf("%p", a)}
} else if h, ok := arg.(*object.Hash); ok {
return &object.String{Value: fmt.Sprintf("%p", h)}
} else if f, ok := arg.(*object.Function); ok {
return &object.String{Value: fmt.Sprintf("%p", f)}
} else if c, ok := arg.(*object.Closure); ok {
return &object.String{Value: fmt.Sprintf("%p", c)}
} else if b, ok := arg.(*object.Builtin); ok {
return &object.String{Value: fmt.Sprintf("%p", b)}
}
return nil
}

View File

@@ -0,0 +1,34 @@
package builtins
import (
"bufio"
"fmt"
"io"
"monkey/internal/object"
"monkey/internal/typing"
"os"
)
// Input ...
func Input(args ...object.Object) object.Object {
if err := typing.Check(
"input", args,
typing.RangeOfArgs(0, 1),
typing.WithTypes(object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
if len(args) == 1 {
prompt := args[0].(*object.String).Value
fmt.Fprintf(os.Stdout, prompt)
}
buffer := bufio.NewReader(os.Stdin)
line, _, err := buffer.ReadLine()
if err != nil && err != io.EOF {
return newError(fmt.Sprintf("error reading input from stdin: %s", err))
}
return &object.String{Value: string(line)}
}

35
internal/builtins/int.go Normal file
View File

@@ -0,0 +1,35 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"strconv"
)
// Int ...
func Int(args ...object.Object) object.Object {
if err := typing.Check(
"int", args,
typing.ExactArgs(1),
); err != nil {
return newError(err.Error())
}
switch arg := args[0].(type) {
case *object.Boolean:
if arg.Value {
return &object.Integer{Value: 1}
}
return &object.Integer{Value: 0}
case *object.Integer:
return arg
case *object.String:
n, err := strconv.ParseInt(arg.Value, 10, 64)
if err != nil {
return newError("could not parse string to int: %s", err)
}
return &object.Integer{Value: n}
default:
return &object.Integer{}
}
}

26
internal/builtins/join.go Normal file
View File

@@ -0,0 +1,26 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"strings"
)
// Join ...
func Join(args ...object.Object) object.Object {
if err := typing.Check(
"join", args,
typing.ExactArgs(2),
typing.WithTypes(object.ARRAY_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
sep := args[1].(*object.String)
a := make([]string, len(arr.Elements))
for i, el := range arr.Elements {
a[i] = el.String()
}
return &object.String{Value: strings.Join(a, sep.Value)}
}

25
internal/builtins/last.go Normal file
View File

@@ -0,0 +1,25 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Last ...
func Last(args ...object.Object) object.Object {
if err := typing.Check(
"last", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
length := len(arr.Elements)
if length > 0 {
return arr.Elements[length-1]
}
return nil
}

21
internal/builtins/len.go Normal file
View File

@@ -0,0 +1,21 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Len ...
func Len(args ...object.Object) object.Object {
if err := typing.Check(
"len", args,
typing.ExactArgs(1),
); err != nil {
return newError(err.Error())
}
if size, ok := args[0].(object.Sizeable); ok {
return &object.Integer{Value: int64(size.Len())}
}
return newError("TypeError: object of type '%s' has no len()", args[0].Type())
}

View File

@@ -0,0 +1,27 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// Listen ...
func Listen(args ...object.Object) object.Object {
if err := typing.Check(
"listen", args,
typing.ExactArgs(2),
typing.WithTypes(object.INTEGER_OBJ, object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
fd := int(args[0].(*object.Integer).Value)
backlog := int(args[1].(*object.Integer).Value)
if err := syscall.Listen(syscall.Handle(fd), backlog); err != nil {
return newError("SocketError: %s", err)
}
return &object.Null{}
}

View File

@@ -0,0 +1,21 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"strings"
)
// Lower ...
func Lower(args ...object.Object) object.Object {
if err := typing.Check(
"lower", args,
typing.ExactArgs(1),
typing.WithTypes(object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
str := args[0].(*object.String)
return &object.String{Value: strings.ToLower(str.Value)}
}

31
internal/builtins/max.go Normal file
View File

@@ -0,0 +1,31 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"sort"
)
// Max ...
func Max(args ...object.Object) object.Object {
if err := typing.Check(
"max", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
a := args[0].(*object.Array)
// TODO: Make this more generic
xs := []int{} //make([]int, len(a.Elements))
for n, e := range a.Elements {
if i, ok := e.(*object.Integer); ok {
xs = append(xs, int(i.Value))
} else {
return newError("item #%d not an `int` got=%s", n, e.Type())
}
}
sort.Ints(xs)
return &object.Integer{Value: int64(xs[len(xs)-1])}
}

31
internal/builtins/min.go Normal file
View File

@@ -0,0 +1,31 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"sort"
)
// Min ...
func Min(args ...object.Object) object.Object {
if err := typing.Check(
"min", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
a := args[0].(*object.Array)
// TODO: Make this more generic
xs := []int{} //make([]int, len(a.Elements))
for n, e := range a.Elements {
if i, ok := e.(*object.Integer); ok {
xs = append(xs, int(i.Value))
} else {
return newError("item #%d not an `int` got=%s", n, e.Type())
}
}
sort.Ints(xs)
return &object.Integer{Value: int64(xs[0])}
}

22
internal/builtins/oct.go Normal file
View File

@@ -0,0 +1,22 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"monkey/internal/typing"
"strconv"
)
// Oct ...
func Oct(args ...object.Object) object.Object {
if err := typing.Check(
"oct", args,
typing.ExactArgs(1),
typing.WithTypes(object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
i := args[0].(*object.Integer)
return &object.String{Value: fmt.Sprintf("0%s", strconv.FormatInt(i.Value, 8))}
}

86
internal/builtins/open.go Normal file
View File

@@ -0,0 +1,86 @@
package builtins
import (
"fmt"
"log"
"monkey/internal/object"
"monkey/internal/typing"
"os"
"syscall"
)
// Close ...
func parseMode(mode string) (int, error) {
var flag int
for _, c := range mode {
switch c {
case 'r':
if (flag & os.O_WRONLY) != 0 {
flag |= os.O_RDWR
} else {
log.Printf("r2")
flag |= os.O_RDONLY
}
case 'w':
if (flag & os.O_RDONLY) != 0 {
flag |= os.O_RDWR
} else {
flag |= os.O_WRONLY
}
case 'a':
flag |= os.O_APPEND
default:
return 0, fmt.Errorf("ValueError: mode string must be one of 'r', 'w', 'a', not '%c'", c)
}
}
if (flag&os.O_WRONLY) != 0 || (flag&os.O_RDWR) != 0 {
log.Printf("c1")
flag |= os.O_CREATE
if (flag & os.O_APPEND) == 0 {
log.Printf("t1")
flag |= os.O_TRUNC
}
}
if !((flag == os.O_RDONLY) || (flag&os.O_WRONLY != 0) || (flag&os.O_RDWR != 0)) {
return 0, fmt.Errorf("ValueError: mode string must be at least one of 'r', 'w', or 'rw'")
}
return flag, nil
}
// Open ...
func Open(args ...object.Object) object.Object {
if err := typing.Check(
"open", args,
typing.RangeOfArgs(1, 2),
typing.WithTypes(object.STRING_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
var (
filename string
mode = "r"
perm uint32 = 0640
)
filename = args[0].(*object.String).Value
if len(args) == 2 {
mode = args[1].(*object.String).Value
}
flag, err := parseMode(mode)
if err != nil {
return newError(err.Error())
}
fd, err := syscall.Open(filename, flag, perm)
if err != nil {
return newError("IOError: %s", err)
}
return &object.Integer{Value: int64(fd)}
}

26
internal/builtins/ord.go Normal file
View File

@@ -0,0 +1,26 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Ord ...
func Ord(args ...object.Object) object.Object {
if err := typing.Check(
"ord", args,
typing.ExactArgs(1),
typing.WithTypes(object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
s := args[0].(*object.String)
if len(s.Value) == 1 {
return &object.Integer{Value: int64(s.Value[0])}
}
return newError(
"TypeError: ord() expected a single character `str` got=%s",
s.Inspect(),
)
}

29
internal/builtins/pop.go Normal file
View File

@@ -0,0 +1,29 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Pop ...
func Pop(args ...object.Object) object.Object {
if err := typing.Check(
"pop", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
length := len(arr.Elements)
if length == 0 {
return newError("IndexError: pop from an empty array")
}
element := arr.Elements[length-1]
arr.Elements = arr.Elements[:length-1]
return element
}

34
internal/builtins/pow.go Normal file
View File

@@ -0,0 +1,34 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
func pow(x, y int64) int64 {
p := int64(1)
for y > 0 {
if y&1 != 0 {
p *= x
}
y >>= 1
x *= x
}
return p
}
// Pow ...
func Pow(args ...object.Object) object.Object {
if err := typing.Check(
"pow", args,
typing.ExactArgs(2),
typing.WithTypes(object.INTEGER_OBJ, object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
x := args[0].(*object.Integer)
y := args[1].(*object.Integer)
value := pow(x.Value, y.Value)
return &object.Integer{Value: value}
}

View File

@@ -0,0 +1,22 @@
package builtins
import (
"fmt"
"monkey/internal/object"
"monkey/internal/typing"
)
// Print ...
func Print(args ...object.Object) object.Object {
if err := typing.Check(
"print", args,
typing.MinimumArgs(1),
typing.WithTypes(object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
fmt.Println(args[0].String())
return nil
}

22
internal/builtins/push.go Normal file
View File

@@ -0,0 +1,22 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Push ...
func Push(args ...object.Object) object.Object {
if err := typing.Check(
"push", args,
typing.ExactArgs(2),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
newArray := arr.Copy()
newArray.Append(args[1])
return newArray
}

40
internal/builtins/read.go Normal file
View File

@@ -0,0 +1,40 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// DefaultBufferSize is the default buffer size
const DefaultBufferSize = 4096
// Read ...
func Read(args ...object.Object) object.Object {
if err := typing.Check(
"read", args,
typing.RangeOfArgs(1, 2),
typing.WithTypes(object.INTEGER_OBJ, object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
var (
fd int
n = DefaultBufferSize
)
fd = int(args[0].(*object.Integer).Value)
if len(args) == 2 {
n = int(args[1].(*object.Integer).Value)
}
buf := make([]byte, n)
n, err := syscall.Read(syscall.Handle(fd), buf)
if err != nil {
return newError("IOError: %s", err)
}
return &object.String{Value: string(buf[:n])}
}

View File

@@ -0,0 +1,26 @@
package builtins
import (
"io/ioutil"
"monkey/internal/object"
"monkey/internal/typing"
)
// ReadFile ...
func ReadFile(args ...object.Object) object.Object {
if err := typing.Check(
"readfile", args,
typing.ExactArgs(1),
typing.WithTypes(object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
filename := args[0].(*object.String).Value
data, err := ioutil.ReadFile(filename)
if err != nil {
return newError("IOError: error reading from file %s: %s", filename, err)
}
return &object.String{Value: string(data)}
}

22
internal/builtins/rest.go Normal file
View File

@@ -0,0 +1,22 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Rest ...
func Rest(args ...object.Object) object.Object {
if err := typing.Check(
"rest", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
newArray := arr.Copy()
newArray.PopLeft()
return newArray
}

View File

@@ -0,0 +1,22 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Reversed ...
func Reversed(args ...object.Object) object.Object {
if err := typing.Check(
"reversed", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
newArray := arr.Copy()
newArray.Reverse()
return newArray
}

37
internal/builtins/seek.go Normal file
View File

@@ -0,0 +1,37 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// Seek ...
func Seek(args ...object.Object) object.Object {
if err := typing.Check(
"seek", args,
typing.RangeOfArgs(1, 3),
typing.WithTypes(object.INTEGER_OBJ, object.INTEGER_OBJ, object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
var (
fd int
whence = 0
)
fd = int(args[0].(*object.Integer).Value)
offset := args[1].(*object.Integer).Value
if len(args) == 3 {
whence = int(args[2].(*object.Integer).Value)
}
offset, err := syscall.Seek(syscall.Handle(fd), offset, whence)
if err != nil {
return newError("IOError: %s", err)
}
return &object.Integer{Value: offset}
}

View File

@@ -0,0 +1,65 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"strings"
"syscall"
)
// Socket ...
func Socket(args ...object.Object) object.Object {
if err := typing.Check(
"socket", args,
typing.ExactArgs(1),
typing.WithTypes(object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
var (
domain int
typ int
proto int
)
arg := args[0].(*object.String).Value
switch strings.ToLower(arg) {
case "unix":
domain = syscall.AF_UNIX
typ = syscall.SOCK_STREAM
proto = 0
case "tcp4":
domain = syscall.AF_INET
typ = syscall.SOCK_STREAM
proto = syscall.IPPROTO_TCP
case "tcp6":
domain = syscall.AF_INET6
typ = syscall.SOCK_STREAM
proto = syscall.IPPROTO_TCP
case "udp4":
domain = syscall.AF_INET
typ = syscall.SOCK_DGRAM
proto = syscall.IPPROTO_UDP
case "udp6":
domain = syscall.AF_INET6
typ = syscall.SOCK_DGRAM
proto = syscall.IPPROTO_UDP
default:
return newError("ValueError: invalid socket type '%s'", arg)
}
fd, err := syscall.Socket(domain, typ, proto)
if err != nil {
return newError("SocketError: %s", err)
}
if domain == syscall.AF_INET || domain == syscall.AF_INET6 {
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
return newError("SocketError: cannot enable SO_REUSEADDR: %s", err)
}
}
return &object.Integer{Value: int64(fd)}
}

View File

@@ -0,0 +1,68 @@
package builtins
import (
"fmt"
"net"
"strconv"
)
func parseAddress(address string) (ip net.IP, port int, err error) {
var (
h string
p string
n int64
)
h, p, err = net.SplitHostPort(address)
if err != nil {
return
}
ip = net.ParseIP(h)
if ip == nil {
var addrs []string
addrs, err = net.LookupHost(address)
if err != nil {
err = fmt.Errorf("error resolving host '%s'", address)
return
}
if len(addrs) == 0 {
err = fmt.Errorf("host not found '%s'", address)
return
}
ip = net.ParseIP(addrs[0])
if ip == nil {
err = fmt.Errorf("invalid IP address '%s'", address)
return
}
}
n, err = strconv.ParseInt(p, 10, 16)
if err != nil {
return
}
port = int(n)
return
}
func parseV4Address(address string) (addr [4]byte, port int, err error) {
var ip net.IP
ip, port, err = parseAddress(address)
if err != nil {
return
}
copy(addr[:], ip.To4()[:4])
return
}
func parseV6Address(address string) (addr [16]byte, port int, err error) {
var ip net.IP
ip, port, err = parseAddress(address)
if err != nil {
return
}
copy(addr[:], ip.To16()[:16])
return
}

View File

@@ -0,0 +1,23 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"sort"
)
// Sorted ...
func Sorted(args ...object.Object) object.Object {
if err := typing.Check(
"sort", args,
typing.ExactArgs(1),
typing.WithTypes(object.ARRAY_OBJ),
); err != nil {
return newError(err.Error())
}
arr := args[0].(*object.Array)
newArray := arr.Copy()
sort.Sort(newArray)
return newArray
}

View File

@@ -0,0 +1,32 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"strings"
)
// Split ...
func Split(args ...object.Object) object.Object {
if err := typing.Check(
"split", args,
typing.RangeOfArgs(1, 2),
typing.WithTypes(object.STRING_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
var sep string
s := args[0].(*object.String).Value
if len(args) == 2 {
sep = args[1].(*object.String).Value
}
tokens := strings.Split(s, sep)
elements := make([]object.Object, len(tokens))
for i, token := range tokens {
elements[i] = &object.String{Value: token}
}
return &object.Array{Elements: elements}
}

18
internal/builtins/str.go Normal file
View File

@@ -0,0 +1,18 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// Str ...
func Str(args ...object.Object) object.Object {
if err := typing.Check(
"str", args,
typing.ExactArgs(1),
); err != nil {
return newError(err.Error())
}
return &object.String{Value: args[0].String()}
}

View File

@@ -0,0 +1,18 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
)
// TypeOf ...
func TypeOf(args ...object.Object) object.Object {
if err := typing.Check(
"type", args,
typing.ExactArgs(1),
); err != nil {
return newError(err.Error())
}
return &object.String{Value: string(args[0].Type())}
}

View File

@@ -0,0 +1,20 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"strings"
)
// Upper ...
func Upper(args ...object.Object) object.Object {
if err := typing.Check(
"upper", args,
typing.ExactArgs(1),
typing.WithTypes(object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
return &object.String{Value: strings.ToUpper(args[0].(*object.String).Value)}
}

View File

@@ -0,0 +1,28 @@
package builtins
import (
"monkey/internal/object"
"monkey/internal/typing"
"syscall"
)
// Write ...
func Write(args ...object.Object) object.Object {
if err := typing.Check(
"write", args,
typing.ExactArgs(2),
typing.WithTypes(object.INTEGER_OBJ, object.INTEGER_OBJ),
); err != nil {
return newError(err.Error())
}
fd := int(args[0].(*object.Integer).Value)
data := []byte(args[1].(*object.String).Value)
n, err := syscall.Write(syscall.Handle(fd), data)
if err != nil {
return newError("IOError: %s", err)
}
return &object.Integer{Value: int64(n)}
}

View File

@@ -0,0 +1,28 @@
package builtins
import (
"io/ioutil"
"monkey/internal/object"
"monkey/internal/typing"
)
// WriteFile ...
func WriteFile(args ...object.Object) object.Object {
if err := typing.Check(
"writefile", args,
typing.ExactArgs(2),
typing.WithTypes(object.STRING_OBJ, object.STRING_OBJ),
); err != nil {
return newError(err.Error())
}
filename := args[0].(*object.String).Value
data := []byte(args[1].(*object.String).Value)
err := ioutil.WriteFile(filename, data, 0755)
if err != nil {
return newError("IOError: error writing file %s: %s", filename, err)
}
return &object.Null{}
}

222
internal/code/code.go Normal file
View File

@@ -0,0 +1,222 @@
package code
import (
"bytes"
"encoding/binary"
"fmt"
)
type Instructions []byte
type Opcode byte
func (o Opcode) String() string {
def, err := Lookup(byte(o))
if err != nil {
return ""
}
return def.Name
}
const (
OpConstant Opcode = iota
OpAdd
OpPop
OpSub
OpMul
OpDiv
OpMod
OpOr
OpAnd
OpNot
OpBitwiseOR
OpBitwiseXOR
OpBitwiseAND
OpBitwiseNOT
OpLeftShift
OpRightShift
OpTrue
OpFalse
OpEqual
OpNotEqual
OpGreaterThan
OpGreaterThanEqual
OpMinus
OpJumpNotTruthy
OpJump
OpNull
OpAssignGlobal
OpAssignLocal
OpGetGlobal
OpSetGlobal
OpArray
OpHash
OpGetItem
OpSetItem
OpCall
OpReturn
OpGetLocal
OpSetLocal
OpGetBuiltin
OpClosure
OpGetFree
OpCurrentClosure
OpLoadModule
)
type Definition struct {
Name string
OperandWidths []int
}
var definitions = map[Opcode]*Definition{
OpConstant: {"OpConstant", []int{2}},
OpAssignGlobal: {"OpAssignGlobal", []int{2}},
OpAssignLocal: {"OpAssignLocal", []int{1}},
OpAdd: {"OpAdd", []int{}},
OpPop: {"OpPop", []int{}},
OpSub: {"OpSub", []int{}},
OpMul: {"OpMul", []int{}},
OpDiv: {"OpDiv", []int{}},
OpMod: {"OpMod", []int{}},
OpOr: {"OpOr", []int{}},
OpAnd: {"OpAnd", []int{}},
OpNot: {"OpNot", []int{}},
OpBitwiseOR: {"OpBitwiseOR", []int{}},
OpBitwiseXOR: {"OpBitwiseXOR", []int{}},
OpBitwiseAND: {"OpBitwiseAND", []int{}},
OpBitwiseNOT: {"OpBitwiseNOT", []int{}},
OpLeftShift: {"OpLeftShift", []int{}},
OpRightShift: {"OpRightShift", []int{}},
OpTrue: {"OpTrue", []int{}},
OpFalse: {"OpFalse", []int{}},
OpEqual: {"OpEqual", []int{}},
OpNotEqual: {"OpNotEqual", []int{}},
OpGreaterThan: {"OpGreaterThan", []int{}},
OpGreaterThanEqual: {"OpGreaterThanEqual", []int{}},
OpMinus: {"OpMinus", []int{}},
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
OpJump: {"OpJump", []int{2}},
OpNull: {"OpNull", []int{}},
OpGetGlobal: {"OpGetGlobal", []int{2}},
OpSetGlobal: {"OpSetGlobal", []int{2}},
OpArray: {"OpArray", []int{2}},
OpHash: {"OpHash", []int{2}},
OpGetItem: {"OpGetItem", []int{}},
OpSetItem: {"OpSetItem", []int{}},
OpCall: {"OpCall", []int{1}},
OpReturn: {"OpReturn", []int{}},
OpGetLocal: {"OpGetLocal", []int{1}},
OpSetLocal: {"OpSetLocal", []int{1}},
OpGetBuiltin: {"OpGetBuiltin", []int{1}},
OpClosure: {"OpClosure", []int{2, 1}},
OpGetFree: {"OpGetFree", []int{1}},
OpCurrentClosure: {"OpCurrentClosure", []int{}},
OpLoadModule: {"OpLoadModule", []int{}},
}
func Lookup(op byte) (*Definition, error) {
def, ok := definitions[Opcode(op)]
if !ok {
return nil, fmt.Errorf("opcode %d undefined", op)
}
return def, nil
}
func Make(op Opcode, operands ...int) []byte {
def, ok := definitions[op]
if !ok {
return []byte{}
}
instructions := 1
for _, w := range def.OperandWidths {
instructions += w
}
instruction := make([]byte, instructions)
instruction[0] = byte(op)
offset := 1
for i, o := range operands {
width := def.OperandWidths[i]
switch width {
case 2:
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
case 1:
instruction[offset] = byte(o)
}
offset += width
}
return instruction
}
func (ins Instructions) String() string {
var out bytes.Buffer
i := 0
for i < len(ins) {
def, err := Lookup(ins[i])
if err != nil {
fmt.Fprintf(&out, "ERROR: %s\n", err)
continue
}
operands, read := ReadOperands(def, ins[i+1:])
fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))
i += 1 + read
}
return out.String()
}
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
operandCount := len(def.OperandWidths)
if len(operands) != operandCount {
return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", len(operands), operandCount)
}
switch operandCount {
case 0:
return def.Name
case 1:
return fmt.Sprintf("%s %d", def.Name, operands[0])
case 2:
return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1])
}
return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
}
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
operands := make([]int, len(def.OperandWidths))
offset := 0
for i, width := range def.OperandWidths {
switch width {
case 2:
operands[i] = int(ReadUint16(ins[offset:]))
case 1:
operands[i] = int(ReadUint8(ins[offset:]))
}
offset += width
}
return operands, offset
}
func ReadUint16(ins Instructions) uint16 {
return binary.BigEndian.Uint16(ins)
}
func ReadUint8(ins Instructions) uint8 {
return ins[0]
}

View File

@@ -0,0 +1,88 @@
package code
import "testing"
func TestMake(t *testing.T) {
test := []struct {
op Opcode
operands []int
expected []byte
}{
{OpConstant, []int{65534}, []byte{byte(OpConstant), 255, 254}},
{OpAdd, []int{}, []byte{byte(OpAdd)}},
{OpGetLocal, []int{255}, []byte{byte(OpGetLocal), 255}},
{OpClosure, []int{65534, 255}, []byte{byte(OpClosure), 255, 254, 255}},
}
for _, tt := range test {
instructions := Make(tt.op, tt.operands...)
if len(instructions) != len(tt.expected) {
t.Errorf("instruction has wrong length. want=%d, got=%d", len(tt.expected), len(instructions))
}
for i, b := range tt.expected {
if instructions[i] != tt.expected[i] {
t.Errorf("wrong byte at pos %d. want=%d, got=%d", i, b, instructions[i])
}
}
}
}
func TestInstructions(t *testing.T) {
instructions := []Instructions{
Make(OpAdd),
Make(OpGetLocal, 1),
Make(OpConstant, 2),
Make(OpConstant, 65535),
Make(OpClosure, 65535, 255),
}
expected := `0000 OpAdd
0001 OpGetLocal 1
0003 OpConstant 2
0006 OpConstant 65535
0009 OpClosure 65535 255
`
concatted := Instructions{}
for _, ins := range instructions {
concatted = append(concatted, ins...)
}
if concatted.String() != expected {
t.Errorf("instructions wrong formatted.\nwant=%q\ngot=%q", expected, concatted.String())
}
}
func TestReadOperands(t *testing.T) {
tests := []struct {
op Opcode
operands []int
bytesRead int
}{
{OpConstant, []int{65535}, 2},
{OpGetLocal, []int{255}, 1},
{OpClosure, []int{65535, 255}, 3},
}
for _, tt := range tests {
instruction := Make(tt.op, tt.operands...)
def, err := Lookup(byte(tt.op))
if err != nil {
t.Fatalf("definition not found: %q\n", err)
}
operandsRead, n := ReadOperands(def, instruction[1:])
if n != tt.bytesRead {
t.Fatalf("n wrong. want=%d, got=%d", tt.bytesRead, n)
}
for i, want := range tt.operands {
if operandsRead[i] != want {
t.Errorf("operand wrong. want=%d, got=%d", want, operandsRead[i])
}
}
}
}

View File

@@ -0,0 +1,651 @@
package compiler
import (
"fmt"
"log"
"monkey/internal/ast"
"monkey/internal/builtins"
"monkey/internal/code"
"monkey/internal/object"
"sort"
"strings"
)
type EmittedInstruction struct {
Opcode code.Opcode
Position int
}
type CompilationScope struct {
instructions code.Instructions
lastInstruction EmittedInstruction
previousInstruction EmittedInstruction
}
type Compiler struct {
Debug bool
l int
constants []object.Object
symbolTable *SymbolTable
scopes []CompilationScope
scopeIndex int
}
func New() *Compiler {
mainScope := CompilationScope{
instructions: code.Instructions{},
lastInstruction: EmittedInstruction{},
previousInstruction: EmittedInstruction{},
}
symbolTable := NewSymbolTable()
for i, builtin := range builtins.BuiltinsIndex {
symbolTable.DefineBuiltin(i, builtin.Name)
}
return &Compiler{
constants: []object.Object{},
symbolTable: symbolTable,
scopes: []CompilationScope{mainScope},
scopeIndex: 0,
}
}
func (c *Compiler) currentInstructions() code.Instructions {
return c.scopes[c.scopeIndex].instructions
}
func NewWithState(s *SymbolTable, constants []object.Object) *Compiler {
compiler := New()
compiler.symbolTable = s
compiler.constants = constants
return compiler
}
func (c *Compiler) Compile(node ast.Node) error {
if c.Debug {
log.Printf(
"%sCompiling %T: %s\n",
strings.Repeat(" ", c.l), node, node.String(),
)
}
switch node := node.(type) {
case *ast.Program:
c.l++
for _, s := range node.Statements {
err := c.Compile(s)
if err != nil {
return err
}
}
case *ast.ExpressionStatement:
c.l++
err := c.Compile(node.Expression)
c.l--
if err != nil {
return err
}
c.emit(code.OpPop)
case *ast.InfixExpression:
if node.Operator == "<" || node.Operator == "<=" {
c.l++
err := c.Compile(node.Right)
c.l--
if err != nil {
return err
}
c.l++
err = c.Compile(node.Left)
c.l--
if err != nil {
return err
}
if node.Operator == "<=" {
c.emit(code.OpGreaterThanEqual)
} else {
c.emit(code.OpGreaterThan)
}
return nil
}
c.l++
err := c.Compile(node.Left)
c.l--
if err != nil {
return err
}
c.l++
err = c.Compile(node.Right)
c.l--
if err != nil {
return err
}
switch node.Operator {
case "+":
c.emit(code.OpAdd)
case "-":
c.emit(code.OpSub)
case "*":
c.emit(code.OpMul)
case "/":
c.emit(code.OpDiv)
case "%":
c.emit(code.OpMod)
case "|":
c.emit(code.OpBitwiseOR)
case "^":
c.emit(code.OpBitwiseXOR)
case "&":
c.emit(code.OpBitwiseAND)
case "<<":
c.emit(code.OpLeftShift)
case ">>":
c.emit(code.OpRightShift)
case "||":
c.emit(code.OpOr)
case "&&":
c.emit(code.OpAnd)
case ">":
c.emit(code.OpGreaterThan)
case ">=":
c.emit(code.OpGreaterThanEqual)
case "==":
c.emit(code.OpEqual)
case "!=":
c.emit(code.OpNotEqual)
default:
return fmt.Errorf("unknown operator %s", node.Operator)
}
case *ast.IntegerLiteral:
integer := &object.Integer{Value: node.Value}
c.emit(code.OpConstant, c.addConstant(integer))
case *ast.Null:
c.emit(code.OpNull)
case *ast.Boolean:
if node.Value {
c.emit(code.OpTrue)
} else {
c.emit(code.OpFalse)
}
case *ast.PrefixExpression:
c.l++
err := c.Compile(node.Right)
c.l--
if err != nil {
return err
}
switch node.Operator {
case "!":
c.emit(code.OpNot)
case "~":
c.emit(code.OpBitwiseNOT)
case "-":
c.emit(code.OpMinus)
default:
return fmt.Errorf("unknown operator %s", node.Operator)
}
case *ast.IfExpression:
c.l++
err := c.Compile(node.Condition)
c.l--
if err != nil {
return err
}
// Emit an `OpJumpNotTruthy` with a bogus value
jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999)
c.l++
err = c.Compile(node.Consequence)
c.l--
if err != nil {
return err
}
if c.lastInstructionIs(code.OpPop) {
c.removeLastPop()
}
// Emit an `OnJump` with a bogus value
jumpPos := c.emit(code.OpJump, 9999)
afterConsequencePos := len(c.currentInstructions())
c.changeOperand(jumpNotTruthyPos, afterConsequencePos)
if node.Alternative == nil {
c.emit(code.OpNull)
} else {
c.l++
err := c.Compile(node.Alternative)
c.l--
if err != nil {
return err
}
if c.lastInstructionIs(code.OpPop) {
c.removeLastPop()
}
}
afterAlternativePos := len(c.currentInstructions())
c.changeOperand(jumpPos, afterAlternativePos)
case *ast.BlockStatement:
c.l++
for _, s := range node.Statements {
err := c.Compile(s)
if err != nil {
return err
}
}
c.l--
if c.lastInstructionIs(code.OpPop) {
c.removeLastPop()
} else {
if !c.lastInstructionIs(code.OpReturn) {
c.emit(code.OpNull)
}
}
case *ast.AssignmentExpression:
if ident, ok := node.Left.(*ast.Identifier); ok {
symbol, ok := c.symbolTable.Resolve(ident.Value)
if !ok {
return fmt.Errorf("undefined variable %s", ident.Value)
}
c.l++
err := c.Compile(node.Value)
c.l--
if err != nil {
return err
}
if symbol.Scope == GlobalScope {
c.emit(code.OpAssignGlobal, symbol.Index)
} else {
c.emit(code.OpAssignLocal, symbol.Index)
}
} else if ie, ok := node.Left.(*ast.IndexExpression); ok {
c.l++
err := c.Compile(ie.Left)
c.l--
if err != nil {
return err
}
c.l++
err = c.Compile(ie.Index)
c.l--
if err != nil {
return err
}
c.l++
err = c.Compile(node.Value)
c.l--
if err != nil {
return err
}
c.emit(code.OpSetItem)
} else {
return fmt.Errorf("expected identifier or index expression got=%s", node.Left)
}
case *ast.BindExpression:
var symbol Symbol
if ident, ok := node.Left.(*ast.Identifier); ok {
symbol, ok = c.symbolTable.Resolve(ident.Value)
if !ok {
symbol = c.symbolTable.Define(ident.Value)
} else {
// Local shadowing of previously defined "free" variable in a
// function now being rebound to a locally scoped variable.
if symbol.Scope == FreeScope {
symbol = c.symbolTable.Define(ident.Value)
}
}
c.l++
err := c.Compile(node.Value)
c.l--
if err != nil {
return err
}
if symbol.Scope == GlobalScope {
c.emit(code.OpSetGlobal, symbol.Index)
} else {
c.emit(code.OpSetLocal, symbol.Index)
}
} else {
return fmt.Errorf("expected identifier got=%s", node.Left)
}
case *ast.Identifier:
symbol, ok := c.symbolTable.Resolve(node.Value)
if !ok {
return fmt.Errorf("undefined varible %s", node.Value)
}
c.loadSymbol(symbol)
case *ast.StringLiteral:
str := &object.String{Value: node.Value}
c.emit(code.OpConstant, c.addConstant(str))
case *ast.ArrayLiteral:
for _, el := range node.Elements {
c.l++
err := c.Compile(el)
c.l--
if err != nil {
return err
}
}
c.emit(code.OpArray, len(node.Elements))
case *ast.HashLiteral:
keys := []ast.Expression{}
for k := range node.Pairs {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
return keys[i].String() < keys[j].String()
})
for _, k := range keys {
c.l++
err := c.Compile(k)
c.l--
if err != nil {
return err
}
c.l++
err = c.Compile(node.Pairs[k])
c.l--
if err != nil {
return err
}
}
c.emit(code.OpHash, len(node.Pairs)*2)
case *ast.IndexExpression:
c.l++
err := c.Compile(node.Left)
c.l--
if err != nil {
return err
}
c.l++
err = c.Compile(node.Index)
c.l--
if err != nil {
return err
}
c.emit(code.OpGetItem)
case *ast.FunctionLiteral:
c.enterScope()
if node.Name != "" {
c.symbolTable.DefineFunctionName(node.Name)
}
for _, p := range node.Parameters {
c.symbolTable.Define(p.Value)
}
c.l++
err := c.Compile(node.Body)
c.l--
if err != nil {
return err
}
if c.lastInstructionIs(code.OpPop) {
c.replaceLastPopWithReturn()
}
// If the function doesn't end with a return statement add one with a
// `return null;` and also handle the edge-case of empty functions.
if !c.lastInstructionIs(code.OpReturn) {
if !c.lastInstructionIs(code.OpNull) {
c.emit(code.OpNull)
}
c.emit(code.OpReturn)
}
freeSymbols := c.symbolTable.FreeSymbols
numLocals := c.symbolTable.numDefinitions
instructions := c.leaveScope()
for _, s := range freeSymbols {
c.loadSymbol(s)
}
compiledFn := &object.CompiledFunction{
Instructions: instructions,
NumLocals: numLocals,
NumParameters: len(node.Parameters),
}
fnIndex := c.addConstant(compiledFn)
c.emit(code.OpClosure, fnIndex, len(freeSymbols))
case *ast.ReturnStatement:
c.l++
err := c.Compile(node.ReturnValue)
c.l--
if err != nil {
return err
}
c.emit(code.OpReturn)
case *ast.CallExpression:
c.l++
err := c.Compile(node.Function)
c.l--
if err != nil {
return err
}
for _, a := range node.Arguments {
err := c.Compile(a)
if err != nil {
return err
}
}
c.emit(code.OpCall, len(node.Arguments))
case *ast.WhileExpression:
jumpConditionPos := len(c.currentInstructions())
c.l++
err := c.Compile(node.Condition)
c.l--
if err != nil {
return err
}
// Emit an `OpJump`with a bogus value
jumpIfFalsePos := c.emit(code.OpJumpNotTruthy, 0xFFFF)
c.l++
err = c.Compile(node.Consequence)
if err != nil {
return err
}
c.l--
// Pop off the LoadNull(s) from ast.BlockStatement(s)
c.emit(code.OpPop)
c.emit(code.OpJump, jumpConditionPos)
afterConsequencePos := c.emit(code.OpNull)
c.changeOperand(jumpIfFalsePos, afterConsequencePos)
case *ast.ImportExpression:
c.l++
err := c.Compile(node.Name)
if err != nil {
return err
}
c.l--
c.emit(code.OpLoadModule)
}
return nil
}
func (c *Compiler) addConstant(obj object.Object) int {
c.constants = append(c.constants, obj)
return len(c.constants) - 1
}
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
ins := code.Make(op, operands...)
pos := c.addInstruction(ins)
c.setLastInstruction(op, pos)
return pos
}
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
previous := c.scopes[c.scopeIndex].lastInstruction
last := EmittedInstruction{Opcode: op, Position: pos}
c.scopes[c.scopeIndex].previousInstruction = previous
c.scopes[c.scopeIndex].lastInstruction = last
}
func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{
Instructions: c.currentInstructions(),
Constants: c.constants,
}
}
func (c *Compiler) addInstruction(ins []byte) int {
postNewInstruction := len(c.currentInstructions())
updatedInstructions := append(c.currentInstructions(), ins...)
c.scopes[c.scopeIndex].instructions = updatedInstructions
return postNewInstruction
}
func (c *Compiler) lastInstructionIs(op code.Opcode) bool {
if len(c.currentInstructions()) == 0 {
return false
}
return c.scopes[c.scopeIndex].lastInstruction.Opcode == op
}
func (c *Compiler) removeLastPop() {
last := c.scopes[c.scopeIndex].lastInstruction
previous := c.scopes[c.scopeIndex].previousInstruction
old := c.currentInstructions()
new := old[:last.Position]
c.scopes[c.scopeIndex].instructions = new
c.scopes[c.scopeIndex].lastInstruction = previous
}
func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) {
ins := c.currentInstructions()
for i := 0; i < len(newInstruction); i++ {
ins[pos+i] = newInstruction[i]
}
}
func (c *Compiler) changeOperand(opPos int, operand int) {
op := code.Opcode(c.currentInstructions()[opPos])
newInstruction := code.Make(op, operand)
c.replaceInstruction(opPos, newInstruction)
}
func (c *Compiler) enterScope() {
scope := CompilationScope{
instructions: code.Instructions{},
lastInstruction: EmittedInstruction{},
previousInstruction: EmittedInstruction{},
}
c.scopes = append(c.scopes, scope)
c.scopeIndex++
c.symbolTable = NewEnclosedSymbolTable(c.symbolTable)
}
func (c *Compiler) leaveScope() code.Instructions {
instructions := c.currentInstructions()
c.scopes = c.scopes[:len(c.scopes)-1]
c.scopeIndex--
c.symbolTable = c.symbolTable.Outer
return instructions
}
func (c *Compiler) replaceLastPopWithReturn() {
lastPos := c.scopes[c.scopeIndex].lastInstruction.Position
c.replaceInstruction(lastPos, code.Make(code.OpReturn))
c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturn
}
type Bytecode struct {
Instructions code.Instructions
Constants []object.Object
}
func (c *Compiler) loadSymbol(s Symbol) {
switch s.Scope {
case GlobalScope:
c.emit(code.OpGetGlobal, s.Index)
case LocalScope:
c.emit(code.OpGetLocal, s.Index)
case BuiltinScope:
c.emit(code.OpGetBuiltin, s.Index)
case FreeScope:
c.emit(code.OpGetFree, s.Index)
case FunctionScope:
c.emit(code.OpCurrentClosure)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
package compiler
type SymbolScope string
const (
LocalScope SymbolScope = "LOCAL"
GlobalScope SymbolScope = "GLOBAL"
BuiltinScope SymbolScope = "BUILTIN"
FreeScope SymbolScope = "FREE"
FunctionScope SymbolScope = "FUNCTION"
)
type Symbol struct {
Name string
Scope SymbolScope
Index int
}
type SymbolTable struct {
Outer *SymbolTable
Store map[string]Symbol
numDefinitions int
FreeSymbols []Symbol
}
func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable {
s := NewSymbolTable()
s.Outer = outer
return s
}
func NewSymbolTable() *SymbolTable {
s := make(map[string]Symbol)
free := []Symbol{}
return &SymbolTable{Store: s, FreeSymbols: free}
}
func (s *SymbolTable) Define(name string) Symbol {
symbol := Symbol{Name: name, Index: s.numDefinitions}
if s.Outer == nil {
symbol.Scope = GlobalScope
} else {
symbol.Scope = LocalScope
}
s.Store[name] = symbol
s.numDefinitions++
return symbol
}
func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
obj, ok := s.Store[name]
if !ok && s.Outer != nil {
obj, ok = s.Outer.Resolve(name)
if !ok {
return obj, ok
}
if obj.Scope == GlobalScope || obj.Scope == BuiltinScope {
return obj, ok
}
free := s.DefineFree(obj)
return free, true
}
return obj, ok
}
func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol {
symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope}
s.Store[name] = symbol
return symbol
}
func (s *SymbolTable) DefineFree(original Symbol) Symbol {
s.FreeSymbols = append(s.FreeSymbols, original)
symbol := Symbol{Name: original.Name, Index: len(s.FreeSymbols) - 1}
symbol.Scope = FreeScope
s.Store[original.Name] = symbol
return symbol
}
func (s *SymbolTable) DefineFunctionName(name string) Symbol {
symbol := Symbol{Name: name, Index: 0, Scope: FunctionScope}
s.Store[name] = symbol
return symbol
}

View File

@@ -0,0 +1,350 @@
package compiler
import "testing"
func TestDefine(t *testing.T) {
expected := map[string]Symbol{
"a": {
Name: "a",
Scope: GlobalScope,
Index: 0,
},
"b": {
Name: "b",
Scope: GlobalScope,
Index: 1,
},
"c": {Name: "c", Scope: LocalScope, Index: 0},
"d": {Name: "d", Scope: LocalScope, Index: 1},
"e": {Name: "e", Scope: LocalScope, Index: 0},
"f": {Name: "f", Scope: LocalScope, Index: 1},
}
global := NewSymbolTable()
a := global.Define("a")
if a != expected["a"] {
t.Errorf("expected a=%+v, got=%+v", expected["a"], a)
}
b := global.Define("b")
if b != expected["b"] {
t.Errorf("expected b=%+v, got=%+v", expected["b"], b)
}
firstLocal := NewEnclosedSymbolTable(global)
c := firstLocal.Define("c")
if c != expected["c"] {
t.Errorf("expected c=%+v, got=%+v", expected["c"], c)
}
d := firstLocal.Define("d")
if d != expected["d"] {
t.Errorf("expected d=%+v, got=%+v", expected["d"], d)
}
secondLocal := NewEnclosedSymbolTable(firstLocal)
e := secondLocal.Define("e")
if e != expected["e"] {
t.Errorf("expected e=%+v, got=%+v", expected["e"], e)
}
f := secondLocal.Define("f")
if f != expected["f"] {
t.Errorf("expected f=%+v, got=%+v", expected["f"], f)
}
}
func TestResolveGlobal(t *testing.T) {
global := NewSymbolTable()
global.Define("a")
global.Define("b")
expected := []Symbol{
Symbol{
Name: "a",
Scope: GlobalScope,
Index: 0,
},
Symbol{
Name: "b",
Scope: GlobalScope,
Index: 1,
},
}
for _, sym := range expected {
result, ok := global.Resolve(sym.Name)
if !ok {
t.Errorf("name %s not resolvable", sym.Name)
}
if result != sym {
t.Errorf("expected %s to resolve to %+v, got=%+v", sym.Name, sym, result)
}
}
}
func TestResolveLocal(t *testing.T) {
global := NewSymbolTable()
global.Define("a")
global.Define("b")
local := NewEnclosedSymbolTable(global)
local.Define("c")
local.Define("d")
expected := []Symbol{
Symbol{Name: "a", Scope: GlobalScope, Index: 0},
Symbol{Name: "b", Scope: GlobalScope, Index: 1},
Symbol{Name: "c", Scope: LocalScope, Index: 0},
Symbol{Name: "d", Scope: LocalScope, Index: 1},
}
for _, sym := range expected {
result, ok := local.Resolve(sym.Name)
if !ok {
t.Errorf("name %s not resolvable", sym.Name)
continue
}
if result != sym {
t.Errorf("expected %s to resolve to %+v, got=%+v",
sym.Name, sym, result)
}
}
}
func TestResolveNestedLocal(t *testing.T) {
global := NewSymbolTable()
global.Define("a")
global.Define("b")
firstLocal := NewEnclosedSymbolTable(global)
firstLocal.Define("c")
firstLocal.Define("d")
secondLocal := NewEnclosedSymbolTable(global)
secondLocal.Define("e")
secondLocal.Define("f")
tests := []struct {
table *SymbolTable
expectedSymbols []Symbol
}{
{
firstLocal,
[]Symbol{
Symbol{Name: "a", Scope: GlobalScope, Index: 0},
Symbol{Name: "b", Scope: GlobalScope, Index: 1},
Symbol{Name: "c", Scope: LocalScope, Index: 0},
Symbol{Name: "d", Scope: LocalScope, Index: 1},
},
},
{
secondLocal,
[]Symbol{
Symbol{Name: "a", Scope: GlobalScope, Index: 0},
Symbol{Name: "b", Scope: GlobalScope, Index: 1},
Symbol{Name: "e", Scope: LocalScope, Index: 0},
Symbol{Name: "f", Scope: LocalScope, Index: 1},
},
},
}
for _, tt := range tests {
for _, sym := range tt.expectedSymbols {
result, ok := tt.table.Resolve(sym.Name)
if !ok {
t.Errorf("name %s not resolvable", sym.Name)
continue
}
if result != sym {
t.Errorf("expected %s to resolve to %+v, got=%+v",
sym.Name, sym, result)
}
}
}
}
func TestDefineResolveBuiltins(t *testing.T) {
global := NewSymbolTable()
firstLocal := NewEnclosedSymbolTable(global)
secondLocal := NewEnclosedSymbolTable(firstLocal)
expected := []Symbol{
Symbol{Name: "a", Scope: BuiltinScope, Index: 0},
Symbol{Name: "c", Scope: BuiltinScope, Index: 1},
Symbol{Name: "e", Scope: BuiltinScope, Index: 2},
Symbol{Name: "f", Scope: BuiltinScope, Index: 3},
}
for i, v := range expected {
global.DefineBuiltin(i, v.Name)
}
for _, table := range []*SymbolTable{global, firstLocal, secondLocal} {
for _, sym := range expected {
result, ok := table.Resolve(sym.Name)
if !ok {
t.Errorf("name %s not resolvable", sym.Name)
continue
}
if result != sym {
t.Errorf("expected %s to resolve to %+v, got=%+v",
sym.Name, sym, result)
}
}
}
}
func TestResolveFree(t *testing.T) {
global := NewSymbolTable()
global.Define("a")
global.Define("b")
firstLocal := NewEnclosedSymbolTable(global)
firstLocal.Define("c")
firstLocal.Define("d")
secondLocal := NewEnclosedSymbolTable(firstLocal)
secondLocal.Define("e")
secondLocal.Define("f")
tests := []struct {
table *SymbolTable
expectedSymbols []Symbol
expectedFreeSymbols []Symbol
}{
{
firstLocal,
[]Symbol{
Symbol{Name: "a", Scope: GlobalScope, Index: 0},
Symbol{Name: "b", Scope: GlobalScope, Index: 1},
Symbol{Name: "c", Scope: LocalScope, Index: 0},
Symbol{Name: "d", Scope: LocalScope, Index: 1},
},
[]Symbol{},
},
{
secondLocal,
[]Symbol{
Symbol{Name: "a", Scope: GlobalScope, Index: 0},
Symbol{Name: "b", Scope: GlobalScope, Index: 1},
Symbol{Name: "c", Scope: FreeScope, Index: 0},
Symbol{Name: "d", Scope: FreeScope, Index: 1},
Symbol{Name: "e", Scope: LocalScope, Index: 0},
Symbol{Name: "f", Scope: LocalScope, Index: 1},
},
[]Symbol{
Symbol{Name: "c", Scope: LocalScope, Index: 0},
Symbol{Name: "d", Scope: LocalScope, Index: 1},
},
},
}
for _, tt := range tests {
for _, sym := range tt.expectedSymbols {
result, ok := tt.table.Resolve(sym.Name)
if !ok {
t.Errorf("name %s not resolvable", sym.Name)
continue
}
if result != sym {
t.Errorf("expected %s to resolve to %+v, got=%+v",
sym.Name, sym, result)
}
}
if len(tt.table.FreeSymbols) != len(tt.expectedFreeSymbols) {
t.Errorf("wrong number of free symbols. got=%d, want=%d",
len(tt.table.FreeSymbols), len(tt.expectedFreeSymbols))
continue
}
for i, sym := range tt.expectedFreeSymbols {
result := tt.table.FreeSymbols[i]
if result != sym {
t.Errorf("wrong free symbol. got=%+v, want=%+v",
result, sym)
}
}
}
}
func TestResolveUnresolvableFree(t *testing.T) {
global := NewSymbolTable()
global.Define("a")
firstLocal := NewEnclosedSymbolTable(global)
firstLocal.Define("c")
secondLocal := NewEnclosedSymbolTable(firstLocal)
secondLocal.Define("e")
secondLocal.Define("f")
expected := []Symbol{
Symbol{Name: "a", Scope: GlobalScope, Index: 0},
Symbol{Name: "c", Scope: FreeScope, Index: 0},
Symbol{Name: "e", Scope: LocalScope, Index: 0},
Symbol{Name: "f", Scope: LocalScope, Index: 1},
}
for _, sym := range expected {
result, ok := secondLocal.Resolve(sym.Name)
if !ok {
t.Errorf("name %s not resolvable", sym.Name)
continue
}
if result != sym {
t.Errorf("expected %s to resolve to %+v, got=%+v",
sym.Name, sym, result)
}
}
expectedUnresolvable := []string{
"b",
"d",
}
for _, name := range expectedUnresolvable {
_, ok := secondLocal.Resolve(name)
if ok {
t.Errorf("name %s resolved, but was expected not to", name)
}
}
}
func TestDefineAndResolveFunctionName(t *testing.T) {
global := NewSymbolTable()
global.DefineFunctionName("a")
expected := Symbol{Name: "a", Scope: FunctionScope, Index: 0}
result, ok := global.Resolve(expected.Name)
if !ok {
t.Fatalf("function name %s not resolvable", expected.Name)
}
if result != expected {
t.Errorf("expected %s to resolve to %+v, got=%+v",
expected.Name, expected, result)
}
}
func TestShadowingFunctionName(t *testing.T) {
global := NewSymbolTable()
global.DefineFunctionName("a")
global.Define("a")
expected := Symbol{Name: "a", Scope: GlobalScope, Index: 0}
result, ok := global.Resolve(expected.Name)
if !ok {
t.Fatalf("function name %s not resolvable", expected.Name)
}
if result != expected {
t.Errorf("expected %s to resolve to %+v, got=%+v",
expected.Name, expected, result)
}
}

View File

@@ -0,0 +1,685 @@
package evaluator
import (
"fmt"
"monkey/internal/ast"
"monkey/internal/builtins"
"monkey/internal/lexer"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/utils"
"os"
"strings"
)
var (
NULL = &object.Null{}
TRUE = &object.Boolean{Value: true}
FALSE = &object.Boolean{Value: false}
)
func isError(obj object.Object) bool {
if obj != nil {
return obj.Type() == object.ERROR_OBJ
}
return false
}
func Eval(node ast.Node, env *object.Environment) object.Object {
switch node := node.(type) {
// Statements
case *ast.Program:
return evalProgram(node, env)
case *ast.ExpressionStatement:
return Eval(node.Expression, env)
case *ast.BlockStatement:
return evalBlockStatements(node, env)
case *ast.IfExpression:
return evalIfExpression(node, env)
case *ast.WhileExpression:
return evalWhileExpression(node, env)
case *ast.ImportExpression:
return evalImportExpression(node, env)
case *ast.ReturnStatement:
val := Eval(node.ReturnValue, env)
if isError(val) {
return val
}
return &object.ReturnValue{Value: val}
case *ast.BindExpression:
value := Eval(node.Value, env)
if isError(value) {
return value
}
if ident, ok := node.Left.(*ast.Identifier); ok {
if immutable, ok := value.(object.Immutable); ok {
env.Set(ident.Value, immutable.Clone())
} else {
env.Set(ident.Value, value)
}
return NULL
}
return newError("expected identifier on left got=%T", node.Left)
case *ast.AssignmentExpression:
left := Eval(node.Left, env)
if isError(left) {
return left
}
value := Eval(node.Value, env)
if isError(value) {
return value
}
if ident, ok := node.Left.(*ast.Identifier); ok {
env.Set(ident.Value, value)
} else if ie, ok := node.Left.(*ast.IndexExpression); ok {
obj := Eval(ie.Left, env)
if isError(obj) {
return obj
}
if array, ok := obj.(*object.Array); ok {
index := Eval(ie.Index, env)
if isError(index) {
return index
}
if idx, ok := index.(*object.Integer); ok {
array.Elements[idx.Value] = value
} else {
return newError("cannot index array with %#v", index)
}
} else if hash, ok := obj.(*object.Hash); ok {
key := Eval(ie.Index, env)
if isError(key) {
return key
}
if hashKey, ok := key.(object.Hashable); ok {
hashed := hashKey.HashKey()
hash.Pairs[hashed] = object.HashPair{Key: key, Value: value}
} else {
return newError("cannot index hash with %T", key)
}
} else {
return newError("object type %T does not support item assignment", obj)
}
} else {
return newError("expected identifier or index expression got=%T", left)
}
return NULL
case *ast.Identifier:
return evalIdentifier(node, env)
case *ast.FunctionLiteral:
params := node.Parameters
body := node.Body
return &object.Function{Parameters: params, Env: env, Body: body}
// Expressions
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}
case *ast.Boolean:
return nativeBoolToBooleanObject(node.Value)
case *ast.Null:
return NULL
case *ast.PrefixExpression:
right := Eval(node.Right, env)
if isError(right) {
return right
}
return evalPrefixExpression(node.Operator, right)
case *ast.InfixExpression:
left := Eval(node.Left, env)
if isError(left) {
return left
}
right := Eval(node.Right, env)
if isError(right) {
return right
}
return evalInfixExpression(node.Operator, left, right)
case *ast.CallExpression:
function := Eval(node.Function, env)
if isError(function) {
return function
}
args := evalExpressions(node.Arguments, env)
if len(args) == 1 && isError(args[0]) {
return args[0]
}
return applyFunction(function, args)
case *ast.StringLiteral:
return &object.String{Value: node.Value}
case *ast.ArrayLiteral:
elements := evalExpressions(node.Elements, env)
if len(elements) == 1 && isError(elements[0]) {
return elements[0]
}
return &object.Array{Elements: elements}
case *ast.IndexExpression:
left := Eval(node.Left, env)
if isError(left) {
return left
}
index := Eval(node.Index, env)
if isError(index) {
return index
}
return evalIndexExpression(left, index)
case *ast.HashLiteral:
return evalHashLiteral(node, env)
}
return nil
}
func evalImportExpression(ie *ast.ImportExpression, env *object.Environment) object.Object {
name := Eval(ie.Name, env)
if isError(name) {
return name
}
if s, ok := name.(*object.String); ok {
attrs := EvalModule(s.Value)
if isError(attrs) {
return attrs
}
return &object.Module{Name: s.Value, Attrs: attrs}
}
return newError("ImportError: invalid import path '%s'", name)
}
func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object {
var result object.Object
for {
condition := Eval(we.Condition, env)
if isError(condition) {
return condition
}
if isTruthy(condition) {
result = Eval(we.Consequence, env)
} else {
break
}
}
if result != nil {
return result
}
return NULL
}
func evalProgram(program *ast.Program, env *object.Environment) object.Object {
var result object.Object
for _, statement := range program.Statements {
result = Eval(statement, env)
switch result := result.(type) {
case *object.ReturnValue:
return result.Value
case *object.Error:
return result
}
}
return result
}
func evalBlockStatements(block *ast.BlockStatement, env *object.Environment) object.Object {
var result object.Object
for _, statement := range block.Statements {
result = Eval(statement, env)
if result != nil {
rt := result.Type()
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
return result
}
}
}
return result
}
func nativeBoolToBooleanObject(input bool) object.Object {
if input {
return TRUE
}
return FALSE
}
func evalPrefixExpression(operator string, right object.Object) object.Object {
switch operator {
case "!":
if right.Type() == object.BOOLEAN_OBJ {
return evalBooleanPrefixOperatorExpression(operator, right)
}
return evalIntegerPrefixOperatorExpression(operator, right)
case "~", "-":
return evalIntegerPrefixOperatorExpression(operator, right)
default:
return newError("unknown operator: %s%s", operator, right.Type())
}
}
func evalBooleanPrefixOperatorExpression(operator string, right object.Object) object.Object {
if right.Type() != object.BOOLEAN_OBJ {
return newError("unknown operator: %s%s", operator, right.Type())
}
switch right {
case TRUE:
return FALSE
case FALSE:
return TRUE
case NULL:
return TRUE
default:
return FALSE
}
}
func evalIntegerPrefixOperatorExpression(operator string, right object.Object) object.Object {
if right.Type() != object.INTEGER_OBJ {
return newError("unknown operator: -%s", right.Type())
}
value := right.(*object.Integer).Value
switch operator {
case "!":
return FALSE
case "~":
return &object.Integer{Value: ^value}
case "-":
return &object.Integer{Value: -value}
default:
return newError("unknown operator: %s", operator)
}
}
func evalInfixExpression(operator string, left, right object.Object) object.Object {
switch {
// {"a": 1} + {"b": 2}
case operator == "+" && left.Type() == object.HASH_OBJ && right.Type() == object.HASH_OBJ:
leftVal := left.(*object.Hash).Pairs
rightVal := right.(*object.Hash).Pairs
pairs := make(map[object.HashKey]object.HashPair)
for k, v := range leftVal {
pairs[k] = v
}
for k, v := range rightVal {
pairs[k] = v
}
return &object.Hash{Pairs: pairs}
// [1] + [2]
case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ:
leftVal := left.(*object.Array).Elements
rightVal := right.(*object.Array).Elements
elements := make([]object.Object, len(leftVal)+len(rightVal))
elements = append(leftVal, rightVal...)
return &object.Array{Elements: elements}
// [1] * 3
case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ:
leftVal := left.(*object.Array).Elements
rightVal := int(right.(*object.Integer).Value)
elements := leftVal
for i := rightVal; i > 1; i-- {
elements = append(elements, leftVal...)
}
return &object.Array{Elements: elements}
// 3 * [1]
case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ:
leftVal := int(left.(*object.Integer).Value)
rightVal := right.(*object.Array).Elements
elements := rightVal
for i := leftVal; i > 1; i-- {
elements = append(elements, rightVal...)
}
return &object.Array{Elements: elements}
// " " * 4
case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ:
leftVal := left.(*object.String).Value
rightVal := right.(*object.Integer).Value
return &object.String{Value: strings.Repeat(leftVal, int(rightVal))}
// 4 * " "
case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ:
leftVal := left.(*object.Integer).Value
rightVal := right.(*object.String).Value
return &object.String{Value: strings.Repeat(rightVal, int(leftVal))}
case operator == "==":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 0)
case operator == "!=":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) != 0)
case operator == "<=":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) < 1)
case operator == ">=":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) > -1)
case operator == "<":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == -1)
case operator == ">":
return nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 1)
case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ:
return evalBooleanInfixExpression(operator, left, right)
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
return evalIntegerInfixExpression(operator, left, right)
case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
return evalStringInfixExpression(operator, left, right)
default:
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
}
func evalBooleanInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(*object.Boolean).Value
rightVal := right.(*object.Boolean).Value
switch operator {
case "&&":
return nativeBoolToBooleanObject(leftVal && rightVal)
case "||":
return nativeBoolToBooleanObject(leftVal || rightVal)
default:
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
}
func evalStringInfixExpression(operator string, left object.Object, right object.Object) object.Object {
leftVal := left.(*object.String).Value
rightVal := right.(*object.String).Value
switch operator {
case "+":
return &object.String{Value: leftVal + rightVal}
default:
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
}
func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(*object.Integer).Value
rightVal := right.(*object.Integer).Value
switch operator {
case "+":
return &object.Integer{Value: leftVal + rightVal}
case "-":
return &object.Integer{Value: leftVal - rightVal}
case "*":
return &object.Integer{Value: leftVal * rightVal}
case "/":
return &object.Integer{Value: leftVal / rightVal}
case "%":
return &object.Integer{Value: leftVal % rightVal}
case "|":
return &object.Integer{Value: leftVal | rightVal}
case "^":
return &object.Integer{Value: leftVal ^ rightVal}
case "&":
return &object.Integer{Value: leftVal & rightVal}
case "<<":
return &object.Integer{Value: leftVal << uint64(rightVal)}
case ">>":
return &object.Integer{Value: leftVal >> uint64(rightVal)}
case "<":
return nativeBoolToBooleanObject(leftVal < rightVal)
case "<=":
return nativeBoolToBooleanObject(leftVal <= rightVal)
case ">":
return nativeBoolToBooleanObject(leftVal > rightVal)
case ">=":
return nativeBoolToBooleanObject(leftVal >= rightVal)
case "==":
return nativeBoolToBooleanObject(leftVal == rightVal)
case "!=":
return nativeBoolToBooleanObject(leftVal != rightVal)
default:
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
}
func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object {
condition := Eval(ie.Condition, env)
if isError(condition) {
return condition
}
if isTruthy(condition) {
return Eval(ie.Consequence, env)
} else if ie.Alternative != nil {
return Eval(ie.Alternative, env)
} else {
return NULL
}
}
func isTruthy(obj object.Object) bool {
switch obj {
case NULL:
return false
case TRUE:
return true
case FALSE:
return false
default:
return true
}
}
func newError(format string, a ...interface{}) *object.Error {
return &object.Error{Message: fmt.Sprintf(format, a...)}
}
// EvalModule evaluates the named module and returns a *object.Module objec
func EvalModule(name string) object.Object {
filename := utils.FindModule(name)
b, err := os.ReadFile(filename)
if err != nil {
return newError("IOError: error reading module '%s': %s", name, err)
}
l := lexer.New(string(b))
p := parser.New(l)
module := p.ParseProgram()
if len(p.Errors()) != 0 {
return newError("ParseError: %s", p.Errors())
}
env := object.NewEnvironment()
Eval(module, env)
return env.ExportedHash()
}
func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
if val, ok := env.Get(node.Value); ok {
return val
}
if builtin, ok := builtins.Builtins[node.Value]; ok {
return builtin
}
return newError("identifier not found: " + node.Value)
}
func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object {
var result []object.Object
for _, e := range exps {
evaluated := Eval(e, env)
if isError(evaluated) {
return []object.Object{evaluated}
}
result = append(result, evaluated)
}
return result
}
func applyFunction(fn object.Object, args []object.Object) object.Object {
switch fn := fn.(type) {
case *object.Function:
extendedEnv := extendFunctionEnv(fn, args)
evaluated := Eval(fn.Body, extendedEnv)
return unwrapReturnValue(evaluated)
case *object.Builtin:
if result := fn.Fn(args...); result != nil {
return result
}
return NULL
default:
return newError("not a function: %s", fn.Type())
}
}
func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment {
env := object.NewEnclosedEnvironment(fn.Env)
for paramIdx, param := range fn.Parameters {
env.Set(param.Value, args[paramIdx])
}
return env
}
func unwrapReturnValue(obj object.Object) object.Object {
if returnValue, ok := obj.(*object.ReturnValue); ok {
return returnValue.Value
}
return obj
}
func evalIndexExpression(left, index object.Object) object.Object {
switch {
case left.Type() == object.STRING_OBJ && index.Type() == object.INTEGER_OBJ:
return evalStringIndexExpression(left, index)
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
return evalArrayIndexExpression(left, index)
case left.Type() == object.HASH_OBJ:
return evalHashIndexExpression(left, index)
case left.Type() == object.MODULE_OBJ:
return EvalModuleIndexExpression(left, index)
default:
return newError("index operator not supported: %s", left.Type())
}
}
func EvalModuleIndexExpression(module, index object.Object) object.Object {
moduleObject := module.(*object.Module)
return evalHashIndexExpression(moduleObject.Attrs, index)
}
func evalStringIndexExpression(str, index object.Object) object.Object {
stringObject := str.(*object.String)
idx := index.(*object.Integer).Value
max := int64((len(stringObject.Value)) - 1)
if idx < 0 || idx > max {
return &object.String{Value: ""}
}
return &object.String{Value: string(stringObject.Value[idx])}
}
func evalHashIndexExpression(hash, index object.Object) object.Object {
hashObject := hash.(*object.Hash)
key, ok := index.(object.Hashable)
if !ok {
return newError("unusable as hash key: %s", index.Type())
}
pair, ok := hashObject.Pairs[key.HashKey()]
if !ok {
return NULL
}
return pair.Value
}
func evalArrayIndexExpression(array, index object.Object) object.Object {
arrayObject := array.(*object.Array)
idx := index.(*object.Integer).Value
maxInx := int64(len(arrayObject.Elements) - 1)
if idx < 0 || idx > maxInx {
return NULL
}
return arrayObject.Elements[idx]
}
func evalHashLiteral(node *ast.HashLiteral, env *object.Environment) object.Object {
pairs := make(map[object.HashKey]object.HashPair)
for keyNode, valueNode := range node.Pairs {
key := Eval(keyNode, env)
if isError(key) {
return key
}
hashKey, ok := key.(object.Hashable)
if !ok {
return newError("unusable as hash key: %s", key.Type())
}
value := Eval(valueNode, env)
if isError(value) {
return value
}
hashed := hashKey.HashKey()
pairs[hashed] = object.HashPair{
Key: key,
Value: value,
}
}
return &object.Hash{Pairs: pairs}
}

View File

@@ -0,0 +1,964 @@
package evaluator
import (
"errors"
"github.com/stretchr/testify/assert"
"monkey/internal/lexer"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/utils"
"os"
"path"
"path/filepath"
"strings"
"testing"
)
var BlacklistedExamples = map[string]bool{
"echoserver": true,
}
func assertEvaluated(t *testing.T, expected interface{}, actual object.Object) {
t.Helper()
assert := assert.New(t)
switch expected.(type) {
case nil:
if _, ok := actual.(*object.Null); ok {
assert.True(ok)
} else {
assert.Equal(expected, actual)
}
case int:
if i, ok := actual.(*object.Integer); ok {
assert.Equal(int64(expected.(int)), i.Value)
} else {
assert.Equal(expected, actual)
}
case error:
if e, ok := actual.(*object.Integer); ok {
assert.Equal(expected.(error).Error(), e.Value)
} else {
assert.Equal(expected, actual)
}
case string:
if s, ok := actual.(*object.String); ok {
assert.Equal(expected.(string), s.Value)
} else {
assert.Equal(expected, actual)
}
default:
t.Fatalf("unsupported type for expected got=%T", expected)
}
}
func TestEvalExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{"5", 5},
{"10", 10},
{"-5", -5},
{"-10", -10},
{"5 + 5 + 5 + 5 - 10", 10},
{"2 * 2 * 2 * 2 * 2", 32},
{"-50 + 100 + -50", 0},
{"5 * 2 + 10", 20},
{"5 + 2 * 10", 25},
{"20 + 2 * -10", 0},
{"50 / 2 * 2 + 10", 60},
{"2 * (5 + 10)", 30},
{"3 * 3 * 3 + 10", 37},
{"3 * (3 * 3) + 10", 37},
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
{"!1", false},
{"~1", -2},
{"5 % 2", 1},
{"1 | 2", 3},
{"2 ^ 4", 6},
{"3 & 6", 2},
{`" " * 4`, " "},
{`4 * " "`, " "},
{"1 << 2", 4},
{"4 >> 2", 1},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
if expected, ok := tt.expected.(int64); ok {
testIntegerObject(t, evaluated, expected)
} else if expected, ok := tt.expected.(bool); ok {
testBooleanObject(t, evaluated, expected)
}
}
}
func TestEvalBooleanExpression(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"true", true},
{"false", false},
{"!true", false},
{"!false", true},
{"true && true", true},
{"false && true", false},
{"true && false", false},
{"false && false", false},
{"true || true", true},
{"false || true", true},
{"true || false", true},
{"false || false", false},
{"1 < 2", true},
{"1 > 2", false},
{"1 < 1", false},
{"1 > 1", false},
{"1 == 1", true},
{"1 != 1", false},
{"1 == 2", false},
{"1 != 2", true},
{"true == true", true},
{"false == false", true},
{"true == false", false},
{"true != false", true},
{"false != true", true},
{"(1 < 2) == true", true},
{"(1 < 2) == false", false},
{"(1 > 2) == true", false},
{"(1 > 2) == false", true},
{"(1 <= 2) == true", true},
{"(1 <= 2) == false", false},
{"(1 >= 2) == true", false},
{"(1 >= 2) == false", true},
{`"a" == "a"`, true},
{`"a" < "b"`, true},
{`"abc" == "abc"`, true},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testBooleanObject(t, evaluated, tt.expected)
}
}
func TestIfElseExpression(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{"if (true) { 10 }", 10},
{"if (false) { 10 }", nil},
{"if (1) { 10 }", 10},
{"if (1 < 2) { 10 }", 10},
{"if (1 > 2) { 10 }", nil},
{"if (1 > 2) { 10 } else { 20 }", 20},
{"if (1 < 2) { 10 } else { 20 }", 10},
{"if (1 < 2) { 10 } else if (1 == 2) { 20 }", 10},
{"if (1 > 2) { 10 } else if (1 == 2) { 20 } else { 30 }", 30},
}
for _, tt := range tests {
t.Log(tt.input)
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func TestReturnStatements(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"return 10;", 10},
{"return 10; 9;", 10},
{"return 2 * 5; 9;", 10},
{"9; return 2 * 5; 9;", 10},
{"if (10 > 1) { return 10; }", 10},
{
`
if (10 > 1) {
if (10 > 1) {
return 10;
}
return 1;
}
`,
10,
},
{
`
f := fn(x) {
return x;
x + 10;
};
f(10);`,
10,
},
{
`
f := fn(x) {
result := x + 10;
return result;
return 10;
};
f(10);`,
20,
},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
func TestErrorHandling(t *testing.T) {
tests := []struct {
input string
expectedMessage string
}{
{
"5 + true;",
"unknown operator: int + bool",
},
{
"5 + true; 5;",
"unknown operator: int + bool",
},
{
"-true",
"unknown operator: -bool",
},
{
"true + false;",
"unknown operator: bool + bool",
},
{
"5; true + false; 5",
"unknown operator: bool + bool",
},
{
"if (10 > 1) { true + false; }",
"unknown operator: bool + bool",
},
{
`
if (10 > 1) {
if (10 > 1) {
return true + false;
}
return 1;
}
`,
"unknown operator: bool + bool",
},
{
"foobar",
"identifier not found: foobar",
},
{
`"Hello" - "World"`,
"unknown operator: str - str",
},
{
`{"name": "Monkey"}[fn(x) { x }];`,
"unusable as hash key: fn",
},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("no error object returned. got=%T(%+v)",
evaluated, evaluated)
continue
}
if errObj.Message != tt.expectedMessage {
t.Errorf("wrong error message. expected=%q, got=%q",
tt.expectedMessage, errObj.Message)
}
}
}
func TestIndexAssignmentStatements(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"xs := [1, 2, 3]; xs[1] = 4; xs[1];", 4},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
func TestAssignmentStatements(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{"a := 0; a = 5;", nil},
{"a := 0; a = 5; a;", 5},
{"a := 0; a = 5 * 5;", nil},
{"a := 0; a = 5 * 5; a;", 25},
{"a := 0; a = 5; b := 0; b = a;", nil},
{"a := 0; a = 5; b := 0; b = a; b;", 5},
{"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5;", nil},
{"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5; c;", 15},
{"a := 5; b := a; a = 0;", nil},
{"a := 5; b := a; a = 0; b;", 5},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func TestBindExpressions(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"a := 5; a;", 5},
{"a := 5 * 5; a;", 25},
{"a := 5; b := a; b;", 5},
{"a := 5; b := a; c := a + b + 5; c;", 15},
}
for _, tt := range tests {
testIntegerObject(t, testEval(tt.input), tt.expected)
}
}
func TestFunctionObject(t *testing.T) {
input := "fn(x) { x + 2; };"
evaluated := testEval(input)
fn, ok := evaluated.(*object.Function)
if !ok {
t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
}
if len(fn.Parameters) != 1 {
t.Fatalf("function has wrong parameters. Parameters=%+v", fn.Parameters)
}
if fn.Parameters[0].String() != "x" {
t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0])
}
expectedBody := "(x + 2)"
if fn.Body.String() != expectedBody {
t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
}
}
func TestFunctionApplication(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"identity := fn(x) { x; }; identity(5);", 5},
{"identity := fn(x) { return x; }; identity(5);", 5},
{"double := fn(x) { x * 2; }; double(5);", 10},
{"add := fn(x, y) { x + y; }; add(5, 5);", 10},
{"add := fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
{"fn(x) { x; }(5)", 5},
}
for _, tt := range tests {
testIntegerObject(t, testEval(tt.input), tt.expected)
}
}
func TestClosures(t *testing.T) {
input := `
newAdder := fn(x) {
fn(y) { x + y };
};
addTwo := newAdder(2);
addTwo(2);`
testIntegerObject(t, testEval(input), 4)
}
func TestStringLiteral(t *testing.T) {
input := `"Hello World!"`
evaluated := testEval(input)
str, ok := evaluated.(*object.String)
if !ok {
t.Fatalf("object is not String. got=%T (+%v)", evaluated, evaluated)
}
if str.Value != "Hello World!" {
t.Errorf("String has wrong value. got=%q", str.Value)
}
}
func TestStringConcatenation(t *testing.T) {
input := `"Hello" + " " + "World!"`
evaluated := testEval(input)
str, ok := evaluated.(*object.String)
if !ok {
t.Fatalf("object is not String. got=%T (+%v)", evaluated, evaluated)
}
if str.Value != "Hello World!" {
t.Errorf("String has wrong value. got=%q", str.Value)
}
}
func TestBuiltinFunctions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{`len("")`, 0},
{`len("four")`, 4},
{`len("hello world")`, 11},
{`len(1)`, errors.New("TypeError: object of type 'int' has no len()")},
{`len("one", "two")`, errors.New("TypeError: len() takes exactly 1 argument (2 given)")},
{`len("∑")`, 1},
{`len([1, 2, 3])`, 3},
{`len([])`, 0},
{`first([1, 2, 3])`, 1},
{`first([])`, nil},
{`first(1)`, errors.New("TypeError: first() expected argument #1 to be `array` got `int`")},
{`last([1, 2, 3])`, 3},
{`last([])`, nil},
{`last(1)`, errors.New("TypeError: last() expected argument #1 to be `array` got `int`")},
{`rest([1, 2, 3])`, []int{2, 3}},
{`rest([])`, nil},
{`push([], 1)`, []int{1}},
{`push(1, 1)`, errors.New("TypeError: push() expected argument #1 to be `array` got `int`")},
{`print("Hello World")`, nil},
{`input()`, ""},
{`pop([])`, errors.New("IndexError: pop from an empty array")},
{`pop([1])`, 1},
{`bool(1)`, true},
{`bool(0)`, false},
{`bool(true)`, true},
{`bool(false)`, false},
{`bool(null)`, false},
{`bool("")`, false},
{`bool("foo")`, true},
{`bool([])`, false},
{`bool([1, 2, 3])`, true},
{`bool({})`, false},
{`bool({"a": 1})`, true},
{`int(true)`, 1},
{`int(false)`, 0},
{`int(1)`, 1},
{`int("10")`, 10},
{`str(null)`, "null"},
{`str(true)`, "true"},
{`str(false)`, "false"},
{`str(10)`, "10"},
{`str("foo")`, "foo"},
{`str([1, 2, 3])`, "[1, 2, 3]"},
{`str({"a": 1})`, "{\"a\": 1}"},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
switch expected := tt.expected.(type) {
case bool:
testBooleanObject(t, evaluated, expected)
case int:
testIntegerObject(t, evaluated, int64(expected))
case string:
testStringObject(t, evaluated, expected)
case error:
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("object is not Error. got=%T (%+v)",
evaluated, evaluated)
continue
}
if errObj.Message != expected.Error() {
t.Errorf("wrong error message. expected=%q, got=%q",
expected, errObj.Message)
}
}
}
}
func TestArrayLiterals(t *testing.T) {
input := "[1, 2 * 2, 3 + 3]"
evaluated := testEval(input)
result, ok := evaluated.(*object.Array)
if !ok {
t.Fatalf("object is not Array. got=%T (+%v)", evaluated, evaluated)
}
if len(result.Elements) != 3 {
t.Fatalf("array has wrong num of elements. got=%d", evaluated)
}
testIntegerObject(t, result.Elements[0], 1)
testIntegerObject(t, result.Elements[1], 4)
testIntegerObject(t, result.Elements[2], 6)
}
func TestArrayDuplication(t *testing.T) {
input := "[1] * 3"
evaluated := testEval(input)
result, ok := evaluated.(*object.Array)
if !ok {
t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
}
if len(result.Elements) != 3 {
t.Fatalf("array has wrong num of elements. got=%d",
len(result.Elements))
}
testIntegerObject(t, result.Elements[0], 1)
testIntegerObject(t, result.Elements[1], 1)
testIntegerObject(t, result.Elements[2], 1)
}
func TestArrayMerging(t *testing.T) {
input := "[1] + [2]"
evaluated := testEval(input)
result, ok := evaluated.(*object.Array)
if !ok {
t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
}
if len(result.Elements) != 2 {
t.Fatalf("array has wrong num of elements. got=%d",
len(result.Elements))
}
testIntegerObject(t, result.Elements[0], 1)
testIntegerObject(t, result.Elements[1], 2)
}
func TestArrayIndexExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{
"[1, 2, 3][0]",
1,
},
{
"[1, 2, 3][1]",
2,
},
{
"[1, 2, 3][2]",
3,
},
{
"i := 0; [1][i];",
1,
},
{
"[1, 2, 3][1 + 1];",
3,
},
{
"myArray := [1, 2, 3]; myArray[2];",
3,
},
{
"myArray := [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
6,
},
{
"myArray := [1, 2, 3]; i := myArray[0]; myArray[i]",
2,
},
{
"[1, 2, 3][3]",
nil,
},
{
"[1, 2, 3][-1]",
nil,
},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func TestHashLiterals(t *testing.T) {
input := `two := "two";
{
"one": 10 - 9,
two: 1 + 1,
"thr" + "ee": 6 / 2,
4: 4,
true: 5,
false: 6
}`
evaluated := testEval(input)
result, ok := evaluated.(*object.Hash)
if !ok {
t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)
}
expected := map[object.HashKey]int64{
(&object.String{Value: "one"}).HashKey(): 1,
(&object.String{Value: "two"}).HashKey(): 2,
(&object.String{Value: "three"}).HashKey(): 3,
(&object.Integer{Value: 4}).HashKey(): 4,
TRUE.HashKey(): 5,
FALSE.HashKey(): 6,
}
if len(result.Pairs) != len(expected) {
t.Fatalf("Hash has wrong num of pair. got=%d", len(result.Pairs))
}
for expectedKey, expectedValue := range expected {
pair, ok := result.Pairs[expectedKey]
if !ok {
t.Errorf("no pair for given key in Pairs")
}
testIntegerObject(t, pair.Value, expectedValue)
}
}
func TestHashMerging(t *testing.T) {
input := `{"a": 1} + {"b": 2}`
evaluated := testEval(input)
result, ok := evaluated.(*object.Hash)
if !ok {
t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)
}
expected := map[object.HashKey]int64{
(&object.String{Value: "a"}).HashKey(): 1,
(&object.String{Value: "b"}).HashKey(): 2,
}
if len(result.Pairs) != len(expected) {
t.Fatalf("Hash has wrong num of pairs. got=%d", len(result.Pairs))
}
for expectedKey, expectedValue := range expected {
pair, ok := result.Pairs[expectedKey]
if !ok {
t.Errorf("no pair for given key in Pairs")
}
testIntegerObject(t, pair.Value, expectedValue)
}
}
func TestHashSelectorExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{
`{"foo": 5}.foo`,
5,
},
{
`{"foo": 5}.bar`,
nil,
},
{
`{}.foo`,
nil,
},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func TestHashIndexExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{
`{"foo": 5}["foo"]`,
5,
},
{
`{"foo": 5}["bar"]`,
nil,
},
{
`key := "foo"; {"foo": 5}[key]`,
5,
},
{
`{}["foo"]`,
nil,
},
{
`{5: 5}[5]`,
5,
},
{
`{true: 5}[true]`,
5,
},
{
`{false: 5}[false]`,
5,
},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func TestWhileExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{"while (false) { }", nil},
{"n := 0; while (n < 10) { n := n + 1 }; n", 10},
{"n := 10; while (n > 0) { n := n - 1 }; n", 0},
{"n := 0; while (n < 10) { n := n + 1 }", nil},
{"n := 10; while (n > 0) { n := n - 1 }", nil},
{"n := 0; while (n < 10) { n = n + 1 }; n", 10},
{"n := 10; while (n > 0) { n = n - 1 }; n", 0},
{"n := 0; while (n < 10) { n = n + 1 }", nil},
{"n := 10; while (n > 0) { n = n - 1 }", nil},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)
program := p.ParseProgram()
env := object.NewEnvironment()
return Eval(program, env)
}
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
result, ok := obj.(*object.Integer)
if !ok {
t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
return false
}
if result.Value != expected {
t.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected)
return false
}
return true
}
func TestNullExpression(t *testing.T) {
evaluated := testEval("null")
testNullObject(t, evaluated)
}
func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
result, ok := obj.(*object.Boolean)
if !ok {
t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj)
return false
}
if result.Value != expected {
t.Errorf("object has wrong value. got=%t, want=%t", result.Value, expected)
return false
}
return true
}
func testNullObject(t *testing.T, obj object.Object) bool {
if obj != NULL {
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
return false
}
return true
}
func testStringObject(t *testing.T, obj object.Object, expected string) bool {
result, ok := obj.(*object.String)
if !ok {
t.Errorf("object is not String. got=%T (%+v)", obj, obj)
return false
}
if result.Value != expected {
t.Errorf("object has wrong value. got=%s, want=%s",
result.Value, expected)
return false
}
return true
}
func TestImportExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{`mod := import("../../testdata/mod"); mod.A`, 5},
{`mod := import("../../testdata/mod"); mod.Sum(2, 3)`, 5},
{`mod := import("../../testdata/mod"); mod.a`, nil},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
assertEvaluated(t, tt.expected, evaluated)
}
}
func TestImportSearchPaths(t *testing.T) {
utils.AddPath("../testdata")
tests := []struct {
input string
expected interface{}
}{
{`mod := import("mod"); mod.A`, 5},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
assertEvaluated(t, tt.expected, evaluated)
}
}
func TestExamples(t *testing.T) {
matches, err := filepath.Glob("../../examples/*.monkey")
if err != nil {
t.Error(err)
}
for _, match := range matches {
basename := path.Base(match)
name := strings.TrimSuffix(basename, filepath.Ext(basename))
if BlacklistedExamples[name] {
t.Skipf("not testing blacklisted example %s", name)
}
t.Run(name, func(t *testing.T) {
b, err := os.ReadFile(match)
if err != nil {
t.Error(err)
}
testEval(string(b))
})
}
}
func TestStringIndexExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{
`"abc"[0]`,
"a",
},
{
`"abc"[1]`,
"b",
},
{
`"abc"[2]`,
"c",
},
{
`i := 0; "abc"[i];`,
"a",
},
{
`"abc"[1 + 1];`,
"c",
},
{
`myString := "abc"; myString[0] + myString[1] + myString[2];`,
"abc",
},
{
`"abc"[3]`,
"",
},
{
`"foo"[-1]`,
"",
},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
str, ok := tt.expected.(string)
if ok {
testStringObject(t, evaluated, string(str))
} else {
testNullObject(t, evaluated)
}
}
}

287
internal/lexer/lexer.go Normal file
View File

@@ -0,0 +1,287 @@
package lexer
import (
"encoding/hex"
"monkey/internal/token"
"strings"
)
type Lexer struct {
input string
position int // current position in input (point to current char)
readPosition int // current reading position in input (after current char)
ch byte // current char under exclamation
prevCh byte // previous char read
}
func New(input string) *Lexer {
l := &Lexer{input: input}
l.readChar()
return l
}
func (l *Lexer) readChar() {
l.prevCh = l.ch
if l.readPosition >= len(l.input) {
l.ch = 0
} else {
l.ch = l.input[l.readPosition]
}
l.position = l.readPosition
l.readPosition += 1
}
func (l *Lexer) peekChar() byte {
if l.readPosition >= len(l.input) {
return 0
} else {
return l.input[l.readPosition]
}
}
func (l *Lexer) NextToken() token.Token {
var tok token.Token
l.skipWhitespace()
switch l.ch {
case '#':
tok.Type = token.COMMENT
tok.Literal = l.readLine()
case '=':
if l.peekChar() == '=' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{
Type: token.EQ,
Literal: literal,
}
} else {
tok = newToken(token.ASSIGN, l.ch)
}
case '+':
tok = newToken(token.PLUS, l.ch)
case '-':
tok = newToken(token.MINUS, l.ch)
case '!':
if l.peekChar() == '=' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{
Type: token.NOT_EQ,
Literal: literal,
}
} else {
tok = newToken(token.NOT, l.ch)
}
case '/':
if l.peekChar() == '/' {
l.readChar()
tok.Type = token.COMMENT
tok.Literal = l.readLine()
} else {
tok = newToken(token.DIVIDE, l.ch)
}
case '*':
tok = newToken(token.MULTIPLY, l.ch)
case '%':
tok = newToken(token.MODULO, l.ch)
case '&':
if l.peekChar() == '&' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{Type: token.AND, Literal: literal}
} else {
tok = newToken(token.BITWISE_AND, l.ch)
}
case '|':
if l.peekChar() == '|' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{Type: token.OR, Literal: literal}
} else {
tok = newToken(token.BITWISE_OR, l.ch)
}
case '^':
tok = newToken(token.BITWISE_XOR, l.ch)
case '~':
tok = newToken(token.BITWISE_NOT, l.ch)
case '<':
if l.peekChar() == '=' {
l.readChar()
tok = newToken(token.LTE, l.ch)
tok.Literal = "<="
} else if l.peekChar() == '<' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{Type: token.LEFT_SHIFT, Literal: literal}
} else {
tok = newToken(token.LT, l.ch)
}
case '>':
if l.peekChar() == '=' {
l.readChar()
tok = newToken(token.GTE, l.ch)
tok.Literal = ">="
} else if l.peekChar() == '>' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{Type: token.RIGHT_SHIFT, Literal: literal}
} else {
tok = newToken(token.GT, l.ch)
}
case ';':
tok = newToken(token.SEMICOLON, l.ch)
case ',':
tok = newToken(token.COMMA, l.ch)
case '.':
tok = newToken(token.DOT, l.ch)
case '(':
tok = newToken(token.LPAREN, l.ch)
case ')':
tok = newToken(token.RPAREN, l.ch)
case '{':
tok = newToken(token.LBRACE, l.ch)
case '}':
tok = newToken(token.RBRACE, l.ch)
case 0:
tok.Literal = ""
tok.Type = token.EOF
case '"':
tok.Type = token.STRING
str, err := l.readString()
if err != nil {
tok = newToken(token.ILLEGAL, l.prevCh)
} else {
tok.Type = token.STRING
tok.Literal = str
}
case '[':
tok = newToken(token.LBRACKET, l.ch)
case ']':
tok = newToken(token.RBRACKET, l.ch)
case ':':
if l.peekChar() == '=' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{Type: token.BIND, Literal: literal}
} else {
tok = newToken(token.COLON, l.ch)
}
default:
if isLetter(l.ch) {
tok.Literal = l.readIdentifier()
tok.Type = token.LookupIdent(tok.Literal)
return tok
} else if isDigit(l.ch) {
tok.Type = token.INT
tok.Literal = l.readNumber()
return tok
} else {
tok = newToken(token.ILLEGAL, l.ch)
}
}
l.readChar()
return tok
}
func newToken(tokenType token.TokenType, ch byte) token.Token {
return token.Token{
Type: tokenType,
Literal: string(ch),
}
}
func (l *Lexer) readIdentifier() string {
position := l.position
for isLetter(l.ch) {
l.readChar()
}
return l.input[position:l.position]
}
func (l *Lexer) readNumber() string {
position := l.position
for isDigit(l.ch) {
l.readChar()
}
return l.input[position:l.position]
}
func isLetter(ch byte) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}
func isDigit(ch byte) bool {
return '0' <= ch && ch <= '9'
}
func (l *Lexer) skipWhitespace() {
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
l.readChar()
}
}
func (l *Lexer) readString() (string, error) {
b := &strings.Builder{}
for {
l.readChar()
if l.ch == '\\' {
switch l.peekChar() {
case '"':
b.WriteByte('"')
case 'n':
b.WriteString("\n")
case 'r':
b.WriteString("\r")
case 't':
b.WriteString("\t")
case '\\':
b.WriteString("\\")
case 'x':
// Skip over the '\\', 'x' and the next two bytes (hex)
l.readChar()
l.readChar()
l.readChar()
src := string([]byte{l.prevCh, l.ch})
dst, err := hex.DecodeString(src)
if err != nil {
return "", err
}
b.Write(dst)
continue
}
// Skip over the '\\' and the matched single escape char
l.readChar()
continue
} else {
if l.ch == '"' || l.ch == 0 {
break
}
}
b.WriteByte(l.ch)
}
return b.String(), nil
}
func (l *Lexer) readLine() string {
position := l.position + 1
for {
l.readChar()
if l.ch == '\r' || l.ch == '\n' || l.ch == 0 {
break
}
}
return l.input[position:l.position]
}

View File

@@ -0,0 +1,212 @@
package lexer
import (
"monkey/internal/token"
"testing"
)
func TestNextToken(t *testing.T) {
input := `#!./monkey-lang
five := 5;
ten := 10;
add := fn(x, y) {
x + y;
};
# this is a comment
result := add(five, ten);
!-/*5;
5 < 10 > 5;
if (5 < 10) {
return true;
} else {
return false;
}
// this is another comment
10 == 10;
10 != 9;
"foobar"
"foo bar"
[1, 2];
{"foo": "bar"}
d.foo
&|^~
!&&||
<<>>
`
tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.COMMENT, "!./monkey-lang"},
{token.IDENT, "five"},
{token.BIND, ":="},
{token.INT, "5"},
{token.SEMICOLON, ";"},
{token.IDENT, "ten"},
{token.BIND, ":="},
{token.INT, "10"},
{token.SEMICOLON, ";"},
{token.IDENT, "add"},
{token.BIND, ":="},
{token.FUNCTION, "fn"},
{token.LPAREN, "("},
{token.IDENT, "x"},
{token.COMMA, ","},
{token.IDENT, "y"},
{token.RPAREN, ")"},
{token.LBRACE, "{"},
{token.IDENT, "x"},
{token.PLUS, "+"},
{token.IDENT, "y"},
{token.SEMICOLON, ";"},
{token.RBRACE, "}"},
{token.SEMICOLON, ";"},
{token.COMMENT, " this is a comment"},
{token.IDENT, "result"},
{token.BIND, ":="},
{token.IDENT, "add"},
{token.LPAREN, "("},
{token.IDENT, "five"},
{token.COMMA, ","},
{token.IDENT, "ten"},
{token.RPAREN, ")"},
{token.SEMICOLON, ";"},
{token.NOT, "!"},
{token.MINUS, "-"},
{token.DIVIDE, "/"},
{token.MULTIPLY, "*"},
{token.INT, "5"},
{token.SEMICOLON, ";"},
{token.INT, "5"},
{token.LT, "<"},
{token.INT, "10"},
{token.GT, ">"},
{token.INT, "5"},
{token.SEMICOLON, ";"},
{token.IF, "if"},
{token.LPAREN, "("},
{token.INT, "5"},
{token.LT, "<"},
{token.INT, "10"},
{token.RPAREN, ")"},
{token.LBRACE, "{"},
{token.RETURN, "return"},
{token.TRUE, "true"},
{token.SEMICOLON, ";"},
{token.RBRACE, "}"},
{token.ELSE, "else"},
{token.LBRACE, "{"},
{token.RETURN, "return"},
{token.FALSE, "false"},
{token.SEMICOLON, ";"},
{token.RBRACE, "}"},
{token.COMMENT, " this is another comment"},
{token.INT, "10"},
{token.EQ, "=="},
{token.INT, "10"},
{token.SEMICOLON, ";"},
{token.INT, "10"},
{token.NOT_EQ, "!="},
{token.INT, "9"},
{token.SEMICOLON, ";"},
{token.STRING, "foobar"},
{token.STRING, "foo bar"},
{token.LBRACKET, "["},
{token.INT, "1"},
{token.COMMA, ","},
{token.INT, "2"},
{token.RBRACKET, "]"},
{token.SEMICOLON, ";"},
{token.LBRACE, "{"},
{token.STRING, "foo"},
{token.COLON, ":"},
{token.STRING, "bar"},
{token.RBRACE, "}"},
{token.IDENT, "d"},
{token.DOT, "."},
{token.IDENT, "foo"},
{token.BITWISE_AND, "&"},
{token.BITWISE_OR, "|"},
{token.BITWISE_XOR, "^"},
{token.BITWISE_NOT, "~"},
{token.NOT, "!"},
{token.AND, "&&"},
{token.OR, "||"},
{token.LEFT_SHIFT, "<<"},
{token.RIGHT_SHIFT, ">>"},
{token.EOF, ""},
}
l := New(input)
for i, tt := range tests {
tok := l.NextToken()
if tok.Type != tt.expectedType {
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
i, tt.expectedType, tok.Type)
}
if tok.Literal != tt.expectedLiteral {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
i, tt.expectedLiteral, tok.Literal)
}
}
}
func TestStringEscapes(t *testing.T) {
input := `#!./monkey-lang
a := "\"foo\""
b := "\x00\x0a\x7f"
c := "\r\n\t"
`
tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.COMMENT, "!./monkey-lang"},
{token.IDENT, "a"},
{token.BIND, ":="},
{token.STRING, "\"foo\""},
{token.IDENT, "b"},
{token.BIND, ":="},
{token.STRING, "\x00\n\u007f"},
{token.IDENT, "c"},
{token.BIND, ":="},
{token.STRING, "\r\n\t"},
{token.EOF, ""},
}
lexer := New(input)
for i, test := range tests {
token := lexer.NextToken()
if token.Type != test.expectedType {
t.Fatalf("tests[%d] - token type wrong. expected=%q, got=%q",
i, test.expectedType, token.Type)
}
if token.Literal != test.expectedLiteral {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
i, test.expectedLiteral, token.Literal)
}
}
}

112
internal/object/array.go Normal file
View File

@@ -0,0 +1,112 @@
package object
import (
"bytes"
"strings"
)
// Array is the array literal type that holds a slice of Object(s)
type Array struct {
Elements []Object
}
func (ao *Array) Type() ObjectType {
return ARRAY_OBJ
}
func (ao *Array) Bool() bool {
return len(ao.Elements) > 0
}
func (a *Array) PopLeft() Object {
if len(a.Elements) > 0 {
e := a.Elements[0]
a.Elements = a.Elements[1:]
return e
}
return &Null{}
}
func (a *Array) PopRight() Object {
if len(a.Elements) > 0 {
e := a.Elements[(len(a.Elements) - 1)]
a.Elements = a.Elements[:(len(a.Elements) - 1)]
return e
}
return &Null{}
}
func (a *Array) Prepend(obj Object) {
a.Elements = append([]Object{obj}, a.Elements...)
}
func (a *Array) Append(obj Object) {
a.Elements = append(a.Elements, obj)
}
func (ao *Array) Inspect() string {
var out bytes.Buffer
var elements []string
for _, e := range ao.Elements {
elements = append(elements, e.Inspect())
}
out.WriteString("[")
out.WriteString(strings.Join(elements, ", "))
out.WriteString("]")
return out.String()
}
func (ao *Array) String() string {
return ao.Inspect()
}
func (ao *Array) Less(i, j int) bool {
if cmp, ok := ao.Elements[i].(Comparable); ok {
return cmp.Compare(ao.Elements[j]) == -1
}
return false
}
func (ao *Array) Compare(other Object) int {
if obj, ok := other.(*Array); ok {
if len(ao.Elements) != len(obj.Elements) {
return -1
}
for i, el := range ao.Elements {
cmp, ok := el.(Comparable)
if !ok {
return -1
}
if cmp.Compare(obj.Elements[i]) != 0 {
return cmp.Compare(obj.Elements[i])
}
}
return 0
}
return -1
}
func (ao *Array) Copy() *Array {
elements := make([]Object, len(ao.Elements))
for i, e := range ao.Elements {
elements[i] = e
}
return &Array{Elements: elements}
}
func (ao *Array) Reverse() {
for i, j := 0, len(ao.Elements)-1; i < j; i, j = i+1, j-1 {
ao.Elements[i], ao.Elements[j] = ao.Elements[j], ao.Elements[i]
}
}
func (ao *Array) Len() int {
return len(ao.Elements)
}
func (ao *Array) Swap(i, j int) {
ao.Elements[i], ao.Elements[j] = ao.Elements[j], ao.Elements[i]
}

43
internal/object/bool.go Normal file
View File

@@ -0,0 +1,43 @@
package object
import (
"fmt"
)
type Boolean struct {
Value bool
}
func (b *Boolean) Bool() bool {
return b.Value
}
func (b *Boolean) Type() ObjectType {
return BOOLEAN_OBJ
}
func (b *Boolean) Inspect() string {
return fmt.Sprintf("%t", b.Value)
}
func (b *Boolean) Clone() Object {
return &Boolean{Value: b.Value}
}
func (b *Boolean) String() string {
return b.Inspect()
}
func (b *Boolean) Int() int {
if b.Value {
return 1
}
return 0
}
func (b *Boolean) Compare(other Object) int {
if obj, ok := other.(*Boolean); ok {
return b.Int() - obj.Int()
}
return 1
}

View File

@@ -0,0 +1,24 @@
package object
import "fmt"
type Builtin struct {
Name string
Fn BuiltinFunction
}
func (b *Builtin) Bool() bool {
return true
}
func (b *Builtin) Type() ObjectType {
return BUILTIN_OBJ
}
func (b *Builtin) Inspect() string {
return fmt.Sprintf("<built-in function %s>", b.Name)
}
func (b *Builtin) String() string {
return b.Inspect()
}

View File

@@ -0,0 +1,49 @@
package object
import (
"fmt"
"monkey/internal/code"
)
type CompiledFunction struct {
Instructions code.Instructions
NumLocals int
NumParameters int
}
func (cf *CompiledFunction) Bool() bool {
return true
}
func (cf *CompiledFunction) Type() ObjectType {
return COMPILED_FUNCTION_OBJ
}
func (cf *CompiledFunction) Inspect() string {
return fmt.Sprintf("CompiledFunction[%p]", cf)
}
func (cf *CompiledFunction) String() string {
return cf.Inspect()
}
type Closure struct {
Fn *CompiledFunction
Free []Object
}
func (c *Closure) Bool() bool {
return true
}
func (c *Closure) Type() ObjectType {
return CLOSURE_OBJ
}
func (c *Closure) Inspect() string {
return fmt.Sprintf("Closure[%p]", c)
}
func (c *Closure) String() string {
return c.Inspect()
}

View File

@@ -0,0 +1,47 @@
package object
import "unicode"
func NewEnclosedEnvironment(outer *Environment) *Environment {
env := NewEnvironment()
env.outer = outer
return env
}
func NewEnvironment() *Environment {
s := make(map[string]Object)
return &Environment{store: s, outer: nil}
}
type Environment struct {
store map[string]Object
outer *Environment
}
func (e *Environment) Get(name string) (Object, bool) {
obj, ok := e.store[name]
if !ok && e.outer != nil {
obj, ok = e.outer.Get(name)
}
return obj, ok
}
func (e *Environment) Set(name string, val Object) Object {
e.store[name] = val
return val
}
// ExportedHash returns a new Hash with the names and values of every publicly
// exported binding in the environment. That is every binding that starts with a
// capital letter. This is used by the module import system to wrap up the
// evaluated module into an object.
func (e *Environment) ExportedHash() *Hash {
pairs := make(map[HashKey]HashPair)
for k, v := range e.store {
if unicode.IsUpper(rune(k[0])) {
s := &String{Value: k}
pairs[s.HashKey()] = HashPair{Key: s, Value: v}
}
}
return &Hash{Pairs: pairs}
}

25
internal/object/error.go Normal file
View File

@@ -0,0 +1,25 @@
package object
type Error struct {
Message string
}
func (e *Error) Bool() bool {
return false
}
func (e *Error) Type() ObjectType {
return ERROR_OBJ
}
func (e *Error) Inspect() string {
return "Error: " + e.Message
}
func (e *Error) Clone() Object {
return &Error{Message: e.Message}
}
func (e *Error) String() string {
return e.Message
}

View File

@@ -0,0 +1,63 @@
package object
import (
"bytes"
"monkey/internal/ast"
"strings"
)
type Function struct {
Parameters []*ast.Identifier
Body *ast.BlockStatement
Env *Environment
}
func (f *Function) Bool() bool {
return false
}
func (f *Function) Type() ObjectType {
return FUNCTION_OBJ
}
func (f *Function) Inspect() string {
var out bytes.Buffer
params := []string{}
for _, p := range f.Parameters {
params = append(params, p.String())
}
out.WriteString("fn")
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(") {\n")
out.WriteString(f.Body.String())
out.WriteString("\n}")
return out.String()
}
func (f *Function) String() string {
return f.Inspect()
}
type ReturnValue struct {
Value Object
}
func (rv *ReturnValue) Bool() bool {
return true
}
func (rv *ReturnValue) Type() ObjectType {
return RETURN_VALUE_OBJ
}
func (rv *ReturnValue) Inspect() string {
return rv.Value.Inspect()
}
func (rv *ReturnValue) String() string {
return rv.Inspect()
}

102
internal/object/hash.go Normal file
View File

@@ -0,0 +1,102 @@
package object
import (
"bytes"
"fmt"
"hash/fnv"
"strings"
)
type HashKey struct {
Type ObjectType
Value uint64
}
func (b *Boolean) HashKey() HashKey {
var value uint64
if b.Value {
value = 1
} else {
value = 0
}
return HashKey{Type: b.Type(), Value: value}
}
func (i *Integer) HashKey() HashKey {
return HashKey{Type: i.Type(), Value: uint64(i.Value)}
}
func (s *String) HashKey() HashKey {
h := fnv.New64a()
h.Write([]byte(s.Value))
return HashKey{Type: s.Type(), Value: h.Sum64()}
}
type HashPair struct {
Key Object
Value Object
}
type Hash struct {
Pairs map[HashKey]HashPair
}
func (h *Hash) Len() int {
return len(h.Pairs)
}
func (h *Hash) Bool() bool {
return len(h.Pairs) > 0
}
func (h *Hash) Type() ObjectType {
return HASH_OBJ
}
func (h *Hash) Inspect() string {
var out bytes.Buffer
pairs := []string{}
for _, pair := range h.Pairs {
pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect()))
}
out.WriteString("{")
out.WriteString(strings.Join(pairs, ", "))
out.WriteString("}")
return out.String()
}
func (h *Hash) String() string {
return h.Inspect()
}
func (h *Hash) Compare(other Object) int {
if obj, ok := other.(*Hash); ok {
if len(h.Pairs) != len(obj.Pairs) {
return -1
}
for _, pair := range h.Pairs {
left := pair.Value
hashed := left.(Hashable)
right, ok := obj.Pairs[hashed.HashKey()]
if !ok {
return -1
}
cmp, ok := left.(Comparable)
if !ok {
return -1
}
if cmp.Compare(right.Value) != 0 {
return cmp.Compare(right.Value)
}
}
return 0
}
return -1
}

41
internal/object/int.go Normal file
View File

@@ -0,0 +1,41 @@
package object
import "fmt"
type Integer struct {
Value int64
}
func (i *Integer) Bool() bool {
return i.Value != 0
}
func (i *Integer) Type() ObjectType {
return INTEGER_OBJ
}
func (i *Integer) Inspect() string {
return fmt.Sprintf("%d", i.Value)
}
func (i *Integer) Clone() Object {
return &Integer{Value: i.Value}
}
func (i *Integer) String() string {
return i.Inspect()
}
func (i *Integer) Compare(other Object) int {
if obj, ok := other.(*Integer); ok {
switch {
case i.Value < obj.Value:
return -1
case i.Value > obj.Value:
return 1
default:
return 0
}
}
return -1
}

29
internal/object/module.go Normal file
View File

@@ -0,0 +1,29 @@
package object
import "fmt"
// Module is the module type used to represent a collection of variabels.
type Module struct {
Name string
Attrs Object
}
func (m Module) String() string {
return m.Inspect()
}
func (m Module) Type() ObjectType {
return MODULE_OBJ
}
func (m Module) Bool() bool {
return true
}
func (m Module) Inspect() string {
return fmt.Sprintf("<module '%s'>", m.Name)
}
func (m Module) Compare(other Object) int {
return 1
}

26
internal/object/null.go Normal file
View File

@@ -0,0 +1,26 @@
package object
type Null struct{}
func (n *Null) Bool() bool {
return false
}
func (n *Null) Type() ObjectType {
return NULL_OBJ
}
func (n *Null) Inspect() string {
return "null"
}
func (n *Null) String() string {
return n.Inspect()
}
func (n *Null) Compare(other Object) int {
if _, ok := other.(*Null); ok {
return 0
}
return 1
}

60
internal/object/object.go Normal file
View File

@@ -0,0 +1,60 @@
package object
import "fmt"
// Type represents the type of an object
type ObjectType string
const (
INTEGER_OBJ = "int"
BOOLEAN_OBJ = "bool"
NULL_OBJ = "null"
RETURN_VALUE_OBJ = "return"
ERROR_OBJ = "error"
FUNCTION_OBJ = "fn"
STRING_OBJ = "str"
BUILTIN_OBJ = "builtin"
ARRAY_OBJ = "array"
HASH_OBJ = "hash"
COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION"
CLOSURE_OBJ = "closure"
MODULE_OBJ = "module"
)
// Comparable is the interface for comparing two Object and their underlying
// values. It is the responsibility of the caller (left) to check for types.
// Returns `true` iif the types and values are identical, `false` otherwise.
type Comparable interface {
Compare(other Object) int
}
// Sizeable is the interface for returning the size of an Object.
// Object(s) that have a valid size must implement this interface and the
// Len() method.
type Sizeable interface {
Len() int
}
// Immutable is the interface for all immutable objects which must implement
// the Clone() method used by binding names to values.
type Immutable interface {
Clone() Object
}
// Object represents a value and implementations are expected to implement
// `Type()` and `Inspect()` functions
type Object interface {
fmt.Stringer
Type() ObjectType
Bool() bool
Inspect() string
}
// Hashable is the interface for all hashable objects which must implement
// the HashKey() method which returns a HashKey result.
type Hashable interface {
HashKey() HashKey
}
// BuiltinFunction represents the builtin function type
type BuiltinFunction func(args ...Object) Object

View File

@@ -0,0 +1,22 @@
package object
import "testing"
func TestStringHashKey(t *testing.T) {
hello1 := &String{Value: "Hello World"}
hello2 := &String{Value: "Hello World"}
diff1 := &String{Value: "My name is johnny"}
diff2 := &String{Value: "My name is johnny"}
if hello1.HashKey() != hello2.HashKey() {
t.Errorf("string with same content have different hash keys")
}
if diff1.HashKey() != diff2.HashKey() {
t.Errorf("string with same content have different hash keys")
}
if hello1.HashKey() == diff1.HashKey() {
t.Errorf("string with different content have same hash keys")
}
}

10
internal/object/state.go Normal file
View File

@@ -0,0 +1,10 @@
package object
import "io"
var (
Arguments []string
StandardInput io.Reader
StandardOutput io.Writer
ExitFunction func(int)
)

48
internal/object/str.go Normal file
View File

@@ -0,0 +1,48 @@
package object
import (
"fmt"
"unicode/utf8"
)
type String struct {
Value string
}
func (s *String) Len() int {
return utf8.RuneCountInString(s.Value)
}
func (s *String) Bool() bool {
return s.Value != ""
}
func (s *String) Type() ObjectType {
return STRING_OBJ
}
func (s *String) Inspect() string {
return fmt.Sprintf("%#v", s.Value)
}
func (s *String) Clone() Object {
return &String{Value: s.Value}
}
func (s *String) String() string {
return s.Value
}
func (s *String) Compare(other Object) int {
if obj, ok := other.(*String); ok {
switch {
case s.Value < obj.Value:
return -1
case s.Value > obj.Value:
return 1
default:
return 0
}
}
return 1
}

621
internal/parser/parser.go Normal file
View File

@@ -0,0 +1,621 @@
package parser
import (
"fmt"
"monkey/internal/ast"
"monkey/internal/lexer"
"monkey/internal/token"
"strconv"
)
const (
_ int = iota
LOWEST
OR
AND
NOT
ASSIGN // := or =
EQUALS // ==
LESSGREATER // > or <
BITWISE_OR // |
BITWISE_XOR // ^
BITWISE_AND // &
BITWISE_SHIFT // << or >>
SUM // + or -
PRODUCT // * or / or %
PREFIX // -X or !X
CALL // myFunction(X)
INDEX // array[index]
)
var precedences = map[token.TokenType]int{
token.OR: OR,
token.AND: AND,
token.NOT: NOT,
token.BIND: ASSIGN,
token.ASSIGN: ASSIGN,
token.EQ: EQUALS,
token.NOT_EQ: EQUALS,
token.LT: LESSGREATER,
token.GT: LESSGREATER,
token.LTE: LESSGREATER,
token.GTE: LESSGREATER,
token.BITWISE_OR: BITWISE_OR,
token.BITWISE_XOR: BITWISE_XOR,
token.BITWISE_AND: BITWISE_AND,
token.LEFT_SHIFT: BITWISE_SHIFT,
token.RIGHT_SHIFT: BITWISE_SHIFT,
token.PLUS: SUM,
token.MINUS: SUM,
token.DIVIDE: PRODUCT,
token.MULTIPLY: PRODUCT,
token.MODULO: PRODUCT,
token.LPAREN: CALL,
token.LBRACKET: INDEX,
token.DOT: INDEX,
}
type (
prefixParseFn func() ast.Expression
infixParseFn func(ast.Expression) ast.Expression
)
type Parser struct {
l *lexer.Lexer
errors []string
curToken token.Token
peekToken token.Token
prefixParseFns map[token.TokenType]prefixParseFn
infixParseFns map[token.TokenType]infixParseFn
}
func New(l *lexer.Lexer) *Parser {
p := &Parser{
l: l,
errors: []string{},
}
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
p.registerPrefix(token.IDENT, p.parseIdentifier)
p.registerPrefix(token.INT, p.parseIntegerLiteral)
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
p.registerPrefix(token.TRUE, p.parseBoolean)
p.registerPrefix(token.FALSE, p.parseBoolean)
p.registerPrefix(token.NULL, p.parseNull)
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
p.registerPrefix(token.IF, p.parseIfExpression)
p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
p.registerPrefix(token.IMPORT, p.parseImportExpression)
p.registerPrefix(token.WHILE, p.parseWhileExpression)
p.registerPrefix(token.STRING, p.parseStringLiteral)
p.registerPrefix(token.LBRACKET, p.parseArrayLiteral)
p.registerPrefix(token.LBRACE, p.parseHashLiteral)
p.infixParseFns = make(map[token.TokenType]infixParseFn)
p.registerInfix(token.PLUS, p.parseInfixExpression)
p.registerInfix(token.MINUS, p.parseInfixExpression)
p.registerInfix(token.DIVIDE, p.parseInfixExpression)
p.registerInfix(token.MULTIPLY, p.parseInfixExpression)
p.registerInfix(token.MODULO, p.parseInfixExpression)
p.registerInfix(token.EQ, p.parseInfixExpression)
p.registerInfix(token.NOT_EQ, p.parseInfixExpression)
p.registerInfix(token.LT, p.parseInfixExpression)
p.registerInfix(token.LTE, p.parseInfixExpression)
p.registerInfix(token.GT, p.parseInfixExpression)
p.registerInfix(token.GTE, p.parseInfixExpression)
p.registerInfix(token.LPAREN, p.parseCallExpression)
p.registerInfix(token.LBRACKET, p.parseIndexExpression)
p.registerInfix(token.BIND, p.parseBindExpression)
p.registerInfix(token.ASSIGN, p.parseAssignmentExpression)
p.registerInfix(token.DOT, p.parseSelectorExpression)
p.registerPrefix(token.BITWISE_NOT, p.parsePrefixExpression)
p.registerInfix(token.BITWISE_OR, p.parseInfixExpression)
p.registerInfix(token.BITWISE_XOR, p.parseInfixExpression)
p.registerInfix(token.BITWISE_AND, p.parseInfixExpression)
p.registerInfix(token.LEFT_SHIFT, p.parseInfixExpression)
p.registerInfix(token.RIGHT_SHIFT, p.parseInfixExpression)
p.registerPrefix(token.NOT, p.parsePrefixExpression)
p.registerInfix(token.OR, p.parseInfixExpression)
p.registerInfix(token.AND, p.parseInfixExpression)
// Read two tokens, so curToken and peekToken are both set
p.nextToken()
p.nextToken()
return p
}
func (p *Parser) nextToken() {
p.curToken = p.peekToken
p.peekToken = p.l.NextToken()
}
func (p *Parser) curTokenIs(t token.TokenType) bool {
return p.curToken.Type == t
}
func (p *Parser) peekTokenIs(t token.TokenType) bool {
return p.peekToken.Type == t
}
func (p *Parser) expectPeek(t token.TokenType) bool {
if p.peekTokenIs(t) {
p.nextToken()
return true
} else {
p.peekError(t)
return false
}
}
func (p *Parser) Errors() []string {
return p.errors
}
func (p *Parser) peekError(t token.TokenType) {
msg := fmt.Sprintf("expected next token to be %s, got %s instead",
t, p.peekToken.Type)
p.errors = append(p.errors, msg)
}
func (p *Parser) noPrefixParseFnError(t token.TokenType) {
msg := fmt.Sprintf("no prefix parse function for %s found", t)
p.errors = append(p.errors, msg)
}
func (p *Parser) noInfixParseFnError(t token.TokenType) {
msg := fmt.Sprintf("no infix parse function for %s found", t)
p.errors = append(p.errors, msg)
}
func (p *Parser) ParseProgram() *ast.Program {
program := &ast.Program{}
program.Statements = []ast.Statement{}
for !p.curTokenIs(token.EOF) {
stmt := p.parseStatement()
if stmt != nil {
program.Statements = append(program.Statements, stmt)
}
p.nextToken()
}
return program
}
func (p *Parser) parseStatement() ast.Statement {
switch p.curToken.Type {
case token.COMMENT:
return p.parseComment()
case token.RETURN:
return p.parseReturnStatement()
default:
return p.parseExpressionStatement()
}
}
func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
stmt := &ast.ReturnStatement{Token: p.curToken}
p.nextToken()
stmt.ReturnValue = p.parseExpression(LOWEST)
if p.peekTokenIs(token.SEMICOLON) {
p.nextToken()
}
return stmt
}
func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
stmt := &ast.ExpressionStatement{Token: p.curToken}
stmt.Expression = p.parseExpression(LOWEST)
if p.peekTokenIs(token.SEMICOLON) {
p.nextToken()
}
return stmt
}
func (p *Parser) parseExpression(precedence int) ast.Expression {
prefix := p.prefixParseFns[p.curToken.Type]
if prefix == nil {
p.noPrefixParseFnError(p.curToken.Type)
return nil
}
leftExp := prefix()
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
infix := p.infixParseFns[p.peekToken.Type]
if infix == nil {
p.noInfixParseFnError(p.peekToken.Type)
//return leftExp
}
p.nextToken()
leftExp = infix(leftExp)
}
return leftExp
}
func (p *Parser) peekPrecedence() int {
if p, ok := precedences[p.peekToken.Type]; ok {
return p
}
return LOWEST
}
func (p *Parser) curPrecedence() int {
if p, ok := precedences[p.curToken.Type]; ok {
return p
}
return LOWEST
}
func (p *Parser) parseIdentifier() ast.Expression {
return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
}
func (p *Parser) parseIntegerLiteral() ast.Expression {
lit := &ast.IntegerLiteral{Token: p.curToken}
value, err := strconv.ParseInt(p.curToken.Literal, 0, 64)
if err != nil {
msg := fmt.Sprintf("could not parse %q as integer", p.curToken.Literal)
p.errors = append(p.errors, msg)
return nil
}
lit.Value = value
return lit
}
func (p *Parser) parsePrefixExpression() ast.Expression {
expression := &ast.PrefixExpression{
Token: p.curToken,
Operator: p.curToken.Literal,
}
p.nextToken()
expression.Right = p.parseExpression(PREFIX)
return expression
}
func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {
expression := &ast.InfixExpression{
Token: p.curToken,
Operator: p.curToken.Literal,
Left: left,
}
precedence := p.curPrecedence()
p.nextToken()
expression.Right = p.parseExpression(precedence)
return expression
}
func (p *Parser) parseBoolean() ast.Expression {
return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)}
}
func (p *Parser) parseGroupedExpression() ast.Expression {
p.nextToken()
exp := p.parseExpression(LOWEST)
if !p.expectPeek(token.RPAREN) {
return nil
}
return exp
}
func (p *Parser) parseIfExpression() ast.Expression {
expression := &ast.IfExpression{Token: p.curToken}
if !p.expectPeek(token.LPAREN) {
return nil
}
p.nextToken()
expression.Condition = p.parseExpression(LOWEST)
if !p.expectPeek(token.RPAREN) {
return nil
}
if !p.expectPeek(token.LBRACE) {
return nil
}
expression.Consequence = p.parseBlockStatement()
if p.peekTokenIs(token.ELSE) {
p.nextToken()
if p.peekTokenIs(token.IF) {
p.nextToken()
expression.Alternative = &ast.BlockStatement{
Statements: []ast.Statement{
&ast.ExpressionStatement{
Expression: p.parseIfExpression(),
},
},
}
return expression
}
if !p.expectPeek(token.LBRACE) {
return nil
}
expression.Alternative = p.parseBlockStatement()
}
return expression
}
func (p *Parser) parseBlockStatement() *ast.BlockStatement {
block := &ast.BlockStatement{Token: p.curToken}
block.Statements = []ast.Statement{}
p.nextToken()
for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) {
stmt := p.parseStatement()
if stmt != nil {
block.Statements = append(block.Statements, stmt)
}
p.nextToken()
}
return block
}
func (p *Parser) parseFunctionLiteral() ast.Expression {
lit := &ast.FunctionLiteral{Token: p.curToken}
if !p.expectPeek(token.LPAREN) {
return nil
}
lit.Parameters = p.parseFunctionParameters()
if !p.expectPeek(token.LBRACE) {
return nil
}
lit.Body = p.parseBlockStatement()
return lit
}
func (p *Parser) parseFunctionParameters() []*ast.Identifier {
identifiers := []*ast.Identifier{}
if p.peekTokenIs(token.RPAREN) {
p.nextToken()
return identifiers
}
p.nextToken()
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
identifiers = append(identifiers, ident)
for p.peekTokenIs(token.COMMA) {
p.nextToken()
p.nextToken()
ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
identifiers = append(identifiers, ident)
}
if !p.expectPeek(token.RPAREN) {
return nil
}
return identifiers
}
func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
exp := &ast.CallExpression{Token: p.curToken, Function: function}
exp.Arguments = p.parseExpressionList(token.RPAREN)
return exp
}
func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression {
list := []ast.Expression{}
if p.peekTokenIs(end) {
p.nextToken()
return list
}
p.nextToken()
list = append(list, p.parseExpression(LOWEST))
for p.peekTokenIs(token.COMMA) {
p.nextToken()
p.nextToken()
list = append(list, p.parseExpression(LOWEST))
}
if !p.expectPeek(end) {
return nil
}
return list
}
func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) {
p.prefixParseFns[tokenType] = fn
}
func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) {
p.infixParseFns[tokenType] = fn
}
func (p *Parser) parseStringLiteral() ast.Expression {
return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
}
func (p *Parser) parseArrayLiteral() ast.Expression {
array := &ast.ArrayLiteral{Token: p.curToken}
array.Elements = p.parseExpressionList(token.RBRACKET)
return array
}
func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
exp := &ast.IndexExpression{Token: p.curToken, Left: left}
p.nextToken()
exp.Index = p.parseExpression(LOWEST)
if !p.expectPeek(token.RBRACKET) {
return nil
}
return exp
}
func (p *Parser) parseHashLiteral() ast.Expression {
hash := &ast.HashLiteral{Token: p.curToken}
hash.Pairs = make(map[ast.Expression]ast.Expression)
for !p.peekTokenIs(token.RBRACE) {
p.nextToken()
key := p.parseExpression(LOWEST)
if !p.expectPeek(token.COLON) {
return nil
}
p.nextToken()
value := p.parseExpression(LOWEST)
hash.Pairs[key] = value
if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) {
return nil
}
}
if !p.expectPeek(token.RBRACE) {
return nil
}
return hash
}
func (p *Parser) parseWhileExpression() ast.Expression {
expression := &ast.WhileExpression{Token: p.curToken}
if !p.expectPeek(token.LPAREN) {
return nil
}
p.nextToken()
expression.Condition = p.parseExpression(LOWEST)
if !p.expectPeek(token.RPAREN) {
return nil
}
if !p.expectPeek(token.LBRACE) {
return nil
}
expression.Consequence = p.parseBlockStatement()
return expression
}
func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression {
switch node := exp.(type) {
case *ast.Identifier, *ast.IndexExpression:
default:
msg := fmt.Sprintf("expected identifier or index expression on left but got %T %#v", node, exp)
p.errors = append(p.errors, msg)
return nil
}
ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp}
p.nextToken()
ae.Value = p.parseExpression(LOWEST)
return ae
}
func (p *Parser) parseComment() ast.Statement {
return &ast.Comment{Token: p.curToken, Value: p.curToken.Literal}
}
func (p *Parser) parseNull() ast.Expression {
return &ast.Null{Token: p.curToken}
}
func (p *Parser) parseSelectorExpression(expression ast.Expression) ast.Expression {
p.expectPeek(token.IDENT)
index := &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
return &ast.IndexExpression{Left: expression, Index: index}
}
func (p *Parser) parseBindExpression(expression ast.Expression) ast.Expression {
switch node := expression.(type) {
case *ast.Identifier:
default:
msg := fmt.Sprintf("expected identifier opn left but got %T %#v", node, expression)
p.errors = append(p.errors, msg)
return nil
}
be := &ast.BindExpression{Token: p.curToken, Left: expression}
p.nextToken()
be.Value = p.parseExpression(LOWEST)
if fl, ok := be.Value.(*ast.FunctionLiteral); ok {
ident := be.Left.(*ast.Identifier)
fl.Name = ident.Value
}
return be
}
func (p *Parser) parseImportExpression() ast.Expression {
expression := &ast.ImportExpression{Token: p.curToken}
if !p.expectPeek(token.LPAREN) {
return nil
}
p.nextToken()
expression.Name = p.parseExpression(LOWEST)
if !p.expectPeek(token.RPAREN) {
return nil
}
return expression
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
package parser
import (
"fmt"
"strings"
)
var traceLevel int = 0
const traceIdentPlaceholder string = "\t"
func identLevel() string {
return strings.Repeat(traceIdentPlaceholder, traceLevel-1)
}
func tracePrint(fs string) {
fmt.Printf("%s%s\n", identLevel(), fs)
}
func incIdent() { traceLevel = traceLevel + 1 }
func decIdent() { traceLevel = traceLevel - 1 }
func trace(msg string) string {
incIdent()
tracePrint("BEGIN " + msg)
return msg
}
func untrace(msg string) {
tracePrint("END " + msg)
decIdent()
}

View File

@@ -0,0 +1,8 @@
package internal
import "monkey/internal/object"
// Hello ...
func Hello(args ...object.Object) object.Object {
return &object.String{Value: "Hello World!"}
}

257
internal/repl/repl.go Normal file
View File

@@ -0,0 +1,257 @@
package repl
// Package repl implements the Read-Eval-Print-Loop or interactive console
// by lexing, parsing and evaluating the input in the interpreter
import (
"bufio"
"fmt"
"io"
"log"
"monkey/internal/compiler"
"monkey/internal/evaluator"
"monkey/internal/lexer"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/vm"
"os"
)
// PROMPT is the REPL prompt displayed for each input
const PROMPT = ">> "
// MonkeyFace is the REPL's face of shock and horror when you encounter a
// parser error :D
const MonkeyFace = ` __,__
.--. .-" "-. .--.
/ .. \/ .-. .-. \/ .. \
| | '| / Y \ |' | |
| \ \ \ 0 | 0 / / / |
\ '- ,\.-"""""""-./, -' /
''-' /_ ^ ^ _\ '-''
| \._ _./ |
\ \ '~' / /
'._ '-=-' _.'
'-----'
`
type Options struct {
Debug bool
Engine string
Interactive bool
}
type REPL struct {
user string
args []string
opts *Options
}
func New(user string, args []string, opts *Options) *REPL {
object.StandardInput = os.Stdin
object.StandardOutput = os.Stdout
object.ExitFunction = os.Exit
return &REPL{user, args, opts}
}
// Eval parses and evalulates the program given by f and returns the resulting
// environment, any errors are printed to stderr
func (r *REPL) Eval(f io.Reader) (env *object.Environment) {
env = object.NewEnvironment()
b, err := io.ReadAll(f)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
return
}
l := lexer.New(string(b))
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(os.Stderr, p.Errors())
return
}
evaluator.Eval(program, env)
return
}
// Exec parses, compiles and executes the program given by f and returns
// the resulting virtual machine, any errors are printed to stderr
func (r *REPL) Exec(f io.Reader) (state *vm.VMState) {
b, err := io.ReadAll(f)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
return
}
state = vm.NewVMState()
l := lexer.New(string(b))
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(os.Stderr, p.Errors())
return
}
c := compiler.NewWithState(state.Symbols, state.Constants)
c.Debug = r.opts.Debug
err = c.Compile(program)
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
return
}
code := c.Bytecode()
state.Constants = code.Constants
machine := vm.NewWithState(code, state)
machine.Debug = r.opts.Debug
err = machine.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
return
}
return
}
// StartEvalLoop starts the REPL in a continious eval loop
func (r *REPL) StartEvalLoop(in io.Reader, out io.Writer, env *object.Environment) {
scanner := bufio.NewScanner(in)
if env == nil {
env = object.NewEnvironment()
}
for {
fmt.Printf(PROMPT)
scanned := scanner.Scan()
if !scanned {
return
}
line := scanner.Text()
l := lexer.New(line)
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(out, p.Errors())
continue
}
obj := evaluator.Eval(program, env)
if _, ok := obj.(*object.Null); !ok {
io.WriteString(out, obj.Inspect())
io.WriteString(out, "\n")
}
}
}
// StartExecLoop starts the REPL in a continious exec loop
func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *vm.VMState) {
scanner := bufio.NewScanner(in)
if state == nil {
state = vm.NewVMState()
}
for {
fmt.Printf(PROMPT)
scanned := scanner.Scan()
if !scanned {
return
}
line := scanner.Text()
l := lexer.New(line)
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(out, p.Errors())
continue
}
c := compiler.NewWithState(state.Symbols, state.Constants)
c.Debug = r.opts.Debug
err := c.Compile(program)
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
return
}
code := c.Bytecode()
state.Constants = code.Constants
machine := vm.NewWithState(code, state)
machine.Debug = r.opts.Debug
err = machine.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
return
}
obj := machine.LastPoppedStackElem()
if _, ok := obj.(*object.Null); !ok {
io.WriteString(out, obj.Inspect())
io.WriteString(out, "\n")
}
}
}
func (r *REPL) Run() {
object.Arguments = make([]string, len(r.args))
copy(object.Arguments, r.args)
if len(r.args) == 0 {
fmt.Printf("Hello %s! This is the Monkey programming language!\n", r.user)
fmt.Printf("Feel free to type in commands\n")
if r.opts.Engine == "eval" {
r.StartEvalLoop(os.Stdin, os.Stdout, nil)
} else {
r.StartExecLoop(os.Stdin, os.Stdout, nil)
}
return
}
if len(r.args) > 0 {
f, err := os.Open(r.args[0])
if err != nil {
log.Fatalf("could not open source file %s: %s", r.args[0], err)
}
// Remove program argument (zero)
r.args = r.args[1:]
object.Arguments = object.Arguments[1:]
if r.opts.Engine == "eval" {
env := r.Eval(f)
if r.opts.Interactive {
r.StartEvalLoop(os.Stdin, os.Stdout, env)
}
} else {
state := r.Exec(f)
if r.opts.Interactive {
r.StartExecLoop(os.Stdin, os.Stdout, state)
}
}
}
}
func printParserErrors(out io.Writer, errors []string) {
io.WriteString(out, MonkeyFace)
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
io.WriteString(out, " parser errors:\n")
for _, msg := range errors {
io.WriteString(out, "\t"+msg+"\n")
}
}

108
internal/token/token.go Normal file
View File

@@ -0,0 +1,108 @@
package token
type TokenType string
type Token struct {
Type TokenType
Literal string
}
const (
ILLEGAL = "ILLEGAL"
EOF = "EOF"
// COMMENT a line comment, e.g: # this is a comment
COMMENT = "COMMENT"
// Identifiers + literals
IDENT = "IDENT" // add, foobar, x, y
INT = "INT" // 123456
STRING = "STRING"
// Operators
BIND = ":="
ASSIGN = "="
PLUS = "+"
MINUS = "-"
MULTIPLY = "*"
DIVIDE = "/"
MODULO = "%"
// Bitwise / Logical operators
// BITWISE_AND AND
BITWISE_AND = "&"
// BITWISE_OR OR
BITWISE_OR = "|"
// BITWISE_XOR XOR
BITWISE_XOR = "^"
// BITWISE_NOT NOT
BITWISE_NOT = "~"
// LeftShift
LEFT_SHIFT = "<<"
// RightShift
RIGHT_SHIFT = ">>"
//
// Comparision operators
//
// NOT the not operator
NOT = "!"
// AND the and operator
AND = "&&"
// OR the or operator
OR = "||"
LT = "<"
LTE = "<="
GT = ">"
GTE = ">="
EQ = "=="
NOT_EQ = "!="
// Delimiters
COMMA = ","
SEMICOLON = ";"
COLON = ":"
DOT = "."
LPAREN = "("
RPAREN = ")"
LBRACE = "{"
RBRACE = "}"
LBRACKET = "["
RBRACKET = "]"
// Keywords
FUNCTION = "FUNCTION"
TRUE = "TRUE"
FALSE = "FALSE"
NULL = "NULL"
IF = "IF"
ELSE = "ELSE"
RETURN = "RETURN"
WHILE = "WHILE"
// IMPORT the `import` keyword (import)
IMPORT = "IMPORT"
)
var keywords = map[string]TokenType{
"fn": FUNCTION,
"true": TRUE,
"false": FALSE,
"if": IF,
"null": NULL,
"else": ELSE,
"return": RETURN,
"while": WHILE,
"import": IMPORT,
}
func LookupIdent(ident string) TokenType {
if tok, ok := keywords[ident]; ok {
return tok
}
return IDENT
}

56
internal/typing/typing.go Normal file
View File

@@ -0,0 +1,56 @@
package typing
import (
"fmt"
"monkey/internal/object"
)
type CheckFunc func(name string, args []object.Object) error
func Check(name string, args []object.Object, checks ...CheckFunc) error {
for _, check := range checks {
if err := check(name, args); err != nil {
return err
}
}
return nil
}
func ExactArgs(n int) CheckFunc {
return func(name string, args []object.Object) error {
if len(args) != n {
return fmt.Errorf("TypeError: %s() takes exactly %d argument (%d given)", name, n, len(args))
}
return nil
}
}
func MinimumArgs(n int) CheckFunc {
return func(name string, args []object.Object) error {
if len(args) < n {
return fmt.Errorf("TypeError: %s() takes a minimum %d arguments (%d given)", name, n, len(args))
}
return nil
}
}
func RangeOfArgs(n, m int) CheckFunc {
return func(name string, args []object.Object) error {
if len(args) < n || len(args) > m {
return fmt.Errorf("TypeError: %s() takes at least %d arguments at most %d (%d given)", name, n, m, len(args))
}
return nil
}
}
func WithTypes(types ...object.ObjectType) CheckFunc {
return func(name string, args []object.Object) error {
for i, t := range types {
if i < len(args) && args[i].Type() != t {
return fmt.Errorf("TypeError: %s() expected argument #%d to be `%s` got `%s`", name, i+1, t, args[i].Type())
}
}
return nil
}
}

53
internal/utils/utils.go Normal file
View File

@@ -0,0 +1,53 @@
package utils
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
)
var SearchPaths []string
func init() {
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("error getting cwd: %s", err)
}
if e := os.Getenv("MONKEYPATH"); e != "" {
tokens := strings.Split(e, ":")
for _, token := range tokens {
AddPath(token) // ignore errors
}
} else {
SearchPaths = append(SearchPaths, cwd)
}
}
func AddPath(path string) error {
path = os.ExpandEnv(filepath.Clean(path))
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
SearchPaths = append(SearchPaths, absPath)
return nil
}
func Exists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func FindModule(name string) string {
basename := fmt.Sprintf("%s.monkey", name)
for _, p := range SearchPaths {
filename := filepath.Join(p, basename)
if Exists(filename) {
return filename
}
}
return ""
}

18
internal/version.go Normal file
View File

@@ -0,0 +1,18 @@
package internal
import (
"fmt"
)
var (
// Version release version
Version = "0.0.1"
// GitCommit will be overwritten automatically by the build system
GitCommit = "HEAD"
)
// FullVersion returns the full version and commit hash
func FullVersion() string {
return fmt.Sprintf("%s@%s", Version, GitCommit)
}

270
internal/vm/cache.go Normal file
View File

@@ -0,0 +1,270 @@
package vm
import (
"container/list"
"fmt"
"sync"
)
type cachedFrame struct {
Key uint // pointer to key
Value *Frame // pointer to value
Frequency uint // frequency of access
}
type frequencyNode struct {
count uint // frequency count - never decreases
valuesList *list.List // valuesList contains pointer to the head of values linked list
inner *list.Element // actual content of the next element
}
// creates a new frequency list node with the given count
func newFrequencyNode(count uint) *frequencyNode {
return &frequencyNode{
count: count,
valuesList: list.New(),
inner: nil,
}
}
type keyRefNode struct {
inner *list.Element // contains the actual value wrapped by a list element
parentFreqNode *list.Element // contains reference to the frequency node element
keyRef uint // contains pointer to the key
valueRef *Frame // value
}
// creates a new KeyRef node which is used to represent the value in linked list
func newKeyRefNode(keyRef uint, valueRef *Frame, parent *list.Element) *keyRefNode {
return &keyRefNode{
inner: nil,
parentFreqNode: parent,
keyRef: keyRef,
valueRef: valueRef,
}
}
// FrameCache implements all the methods and data-structures required for a LFU cache for caching frames.
type FrameCache struct {
rwLock sync.RWMutex // rwLock is a read-write mutex which provides concurrent reads but exclusive writes
lookupTable map[uint]*keyRefNode // a hash table of <KeyType, *ValueType> for quick reference of values based on keys
frequencies *list.List // internal linked list that contains frequency mapping
maxSize uint // maxSize represents the maximum number of elements that can be in the cache before eviction
}
// MaxSize returns the maximum size of the cache at that point in time
func (lfu *FrameCache) MaxSize() uint {
lfu.rwLock.RLock()
defer lfu.rwLock.RUnlock()
return lfu.maxSize
}
// CurrentSize returns the number of elements in that cache
func (lfu *FrameCache) CurrentSize() uint {
lfu.rwLock.RLock()
defer lfu.rwLock.RUnlock()
return uint(len(lfu.lookupTable))
}
// IsFull checks if the LFU cache is full
func (lfu *FrameCache) IsFull() bool {
lfu.rwLock.RLock()
defer lfu.rwLock.RUnlock()
return uint(len(lfu.lookupTable)) == lfu.maxSize
}
// SetMaxSize updates the max size of the LFU cache
func (lfu *FrameCache) SetMaxSize(size uint) {
lfu.rwLock.Lock()
defer lfu.rwLock.Unlock()
lfu.maxSize = size
}
// evict the least recently used element from the cache, this function is unsafe to be called externally
// because it doesn't provide locking mechanism.
func (lfu *FrameCache) unsafeEvict() error {
// WARNING: This function assumes that a write lock has been held by the caller already
// get the head node of the list
headFreq := lfu.frequencies.Front()
if headFreq == nil {
// list is empty, this is a very unusual condition
return fmt.Errorf("internal error: failed to evict, empty frequency list")
}
headFreqInner := (headFreq.Value).(*frequencyNode)
if headFreqInner.valuesList.Len() == 0 {
// again this is a very unusual condition
return fmt.Errorf("internal error: failed to evict, empty values list")
}
headValuesList := headFreqInner.valuesList
// pop the head of this this values list
headValueNode := headValuesList.Front()
removeResult := headValuesList.Remove(headValueNode).(*keyRefNode)
// update the values list
headFreqInner.valuesList = headValuesList
if headFreqInner.valuesList.Len() == 0 && headFreqInner.count > 1 {
// this node can be removed from the frequency list
freqList := lfu.frequencies
freqList.Remove(headFreq)
lfu.frequencies = freqList
}
// remove the key from lookup table
key := removeResult.keyRef
delete(lfu.lookupTable, key)
return nil
}
// Put method inserts a `<KeyType, ValueType>` to the LFU cache and updates internal
// data structures to keep track of access frequencies, if the cache is full, it evicts the
// least frequently used value from the cache.
func (lfu *FrameCache) Put(key uint, value *Frame) error {
// get write lock
lfu.rwLock.Lock()
defer lfu.rwLock.Unlock()
if _, ok := lfu.lookupTable[key]; ok {
// update the cache value
lfu.lookupTable[key].valueRef = value
return nil
}
if lfu.maxSize == uint(len(lfu.lookupTable)) {
lfu.unsafeEvict()
}
valueNode := newKeyRefNode(key, value, nil)
head := lfu.frequencies.Front()
if head == nil {
// fresh linked list
freqNode := newFrequencyNode(1)
head = lfu.frequencies.PushFront(freqNode)
freqNode.inner = head
} else {
node := head.Value.(*frequencyNode)
if node.count != 1 {
freqNode := newFrequencyNode(1)
head = lfu.frequencies.PushFront(freqNode)
freqNode.inner = head
}
}
valueNode.parentFreqNode = head
node := head.Value.(*frequencyNode)
head = node.valuesList.PushBack(valueNode)
valueNode.inner = head
lfu.lookupTable[key] = valueNode
return nil
}
// Evict can be called to manually perform eviction
func (lfu *FrameCache) Evict() error {
lfu.rwLock.Lock()
defer lfu.rwLock.Unlock()
return lfu.unsafeEvict()
}
func (lfu *FrameCache) unsafeUpdateFrequency(valueNode *keyRefNode) {
parentFreqNode := valueNode.parentFreqNode
currentNode := parentFreqNode.Value.(*frequencyNode)
nextParentFreqNode := parentFreqNode.Next()
var newParent *list.Element = nil
if nextParentFreqNode == nil {
// this is the last node
// create a new node with frequency + 1
newFreqNode := newFrequencyNode(currentNode.count + 1)
lfu.frequencies.PushBack(newFreqNode)
newParent = parentFreqNode.Next()
} else {
nextNode := nextParentFreqNode.Value.(*frequencyNode)
if nextNode.count == (currentNode.count + 1) {
newParent = nextParentFreqNode
} else {
// insert a node in between
newFreqNode := newFrequencyNode(currentNode.count + 1)
lfu.frequencies.InsertAfter(newFreqNode, parentFreqNode)
newParent = parentFreqNode.Next()
}
}
// remove from the existing list
currentNode.valuesList.Remove(valueNode.inner)
newParentNode := newParent.Value.(*frequencyNode)
valueNode.parentFreqNode = newParent
newValueNode := newParentNode.valuesList.PushBack(valueNode)
valueNode.inner = newValueNode
// check if the current node is empty
if currentNode.valuesList.Len() == 0 {
// remove the current node
lfu.frequencies.Remove(parentFreqNode)
}
}
// Get can be called to obtain the value for given key
func (lfu *FrameCache) Get(key uint) (*Frame, bool) {
lfu.rwLock.Lock()
defer lfu.rwLock.Unlock()
// check if data is in the map
valueNode, found := lfu.lookupTable[key]
if !found {
return nil, false
}
lfu.unsafeUpdateFrequency(valueNode)
return valueNode.valueRef, true
}
// Delete removes the specified entry from LFU cache
func (lfu *FrameCache) Delete(key uint) error {
lfu.rwLock.Lock()
defer lfu.rwLock.Unlock()
// check if the key is in the map
valueNode, found := lfu.lookupTable[key]
if !found {
return fmt.Errorf("key %v not found", key)
}
parentFreqNode := valueNode.parentFreqNode
currentNode := (parentFreqNode.Value).(*frequencyNode)
currentNode.valuesList.Remove(valueNode.inner)
if currentNode.valuesList.Len() == 0 {
lfu.frequencies.Remove(parentFreqNode)
}
delete(lfu.lookupTable, key)
return nil
}
// NewFrameCache ...
func NewFrameCache(maxSize uint) *FrameCache {
return &FrameCache{
rwLock: sync.RWMutex{},
lookupTable: make(map[uint]*keyRefNode),
maxSize: maxSize,
frequencies: list.New(),
}
}

46
internal/vm/frame.go Normal file
View File

@@ -0,0 +1,46 @@
package vm
import (
"monkey/internal/code"
"monkey/internal/object"
"unsafe"
)
var cache = NewFrameCache(32)
type Frame struct {
cl *object.Closure
ip int
basePointer int
}
func NewFrame(cl *object.Closure, basePointer int) *Frame {
key := uint(uintptr(unsafe.Pointer(cl))) + uint(basePointer)
if frame, ok := cache.Get(key); ok {
frame.Reset()
return frame
}
frame := &Frame{
cl: cl,
ip: -1,
basePointer: basePointer,
}
cache.Put(key, frame)
return frame
}
// NextOp ...
func (f *Frame) NextOp() code.Opcode {
return code.Opcode(f.Instructions()[f.ip+1])
}
func (f *Frame) Reset() {
f.ip = -1
}
func (f *Frame) Instructions() code.Instructions {
return f.cl.Fn.Instructions
}

928
internal/vm/vm.go Normal file
View File

@@ -0,0 +1,928 @@
package vm
import (
"fmt"
"io/ioutil"
"log"
"monkey/internal/builtins"
"monkey/internal/code"
"monkey/internal/compiler"
"monkey/internal/lexer"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/utils"
"strings"
"unicode"
)
const StackSize = 2048
const GlobalsSize = 65536
const MaxFrames = 1024
var Null = &object.Null{}
var True = &object.Boolean{Value: true}
var False = &object.Boolean{Value: false}
// ExecModule compiles the named module and returns a *object.Module object
func ExecModule(name string, state *VMState) (object.Object, error) {
filename := utils.FindModule(name)
if filename == "" {
return nil, fmt.Errorf("ImportError: no module named '%s'", name)
}
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("IOError: error reading module '%s': %s", name, err)
}
l := lexer.New(string(b))
p := parser.New(l)
module := p.ParseProgram()
if len(p.Errors()) != 0 {
return nil, fmt.Errorf("ParseError: %s", p.Errors())
}
c := compiler.NewWithState(state.Symbols, state.Constants)
err = c.Compile(module)
if err != nil {
return nil, fmt.Errorf("CompileError: %s", err)
}
code := c.Bytecode()
state.Constants = code.Constants
machine := NewWithState(code, state)
err = machine.Run()
if err != nil {
return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err)
}
return state.ExportedHash(), nil
}
type VMState struct {
Constants []object.Object
Globals []object.Object
Symbols *compiler.SymbolTable
}
func NewVMState() *VMState {
symbolTable := compiler.NewSymbolTable()
for i, builtin := range builtins.BuiltinsIndex {
symbolTable.DefineBuiltin(i, builtin.Name)
}
return &VMState{
Constants: []object.Object{},
Globals: make([]object.Object, GlobalsSize),
Symbols: symbolTable,
}
}
// exported binding in the vm state. That is every binding that starts with a
// capital letter. This is used by the module import system to wrap up the
// compiled and evaulated module into an object.
func (s *VMState) ExportedHash() *object.Hash {
pairs := make(map[object.HashKey]object.HashPair)
for name, symbol := range s.Symbols.Store {
if unicode.IsUpper(rune(name[0])) {
if symbol.Scope == compiler.GlobalScope {
obj := s.Globals[symbol.Index]
s := &object.String{Value: name}
pairs[s.HashKey()] = object.HashPair{Key: s, Value: obj}
}
}
}
return &object.Hash{Pairs: pairs}
}
type VM struct {
Debug bool
state *VMState
stack []object.Object
sp int // Always points to the next value. Top of stack is stack[sp-1]
frames []*Frame
frame *Frame // Current frame or nil
fp int // Always points to the current frame. Current frame is frames[fp-1]
}
// New constructs a new monkey-lang bytecode virtual machine
func New(bytecode *compiler.Bytecode) *VM {
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := &object.Closure{Fn: mainFn}
mainFrame := NewFrame(mainClosure, 0)
frames := make([]*Frame, MaxFrames)
frames[0] = mainFrame
state := NewVMState()
state.Constants = bytecode.Constants
return &VM{
state: state,
stack: make([]object.Object, StackSize),
sp: 0,
frames: frames,
frame: mainFrame,
fp: 1,
}
}
func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := &object.Closure{Fn: mainFn}
mainFrame := NewFrame(mainClosure, 0)
frames := make([]*Frame, MaxFrames)
frames[0] = mainFrame
return &VM{
state: state,
frames: frames,
frame: mainFrame,
fp: 1,
stack: make([]object.Object, StackSize),
sp: 0,
}
}
func (vm *VM) pushFrame(f *Frame) {
vm.frame = f
vm.frames[vm.fp] = f
vm.fp++
}
func (vm *VM) popFrame() *Frame {
vm.fp--
vm.frame = vm.frames[vm.fp-1]
return vm.frames[vm.fp]
}
func (vm *VM) loadModule(name object.Object) error {
s, ok := name.(*object.String)
if !ok {
return fmt.Errorf(
"TypeError: import() expected argument #1 to be `str` got `%s`",
name.Type(),
)
}
attrs, err := ExecModule(s.Value, vm.state)
if err != nil {
return err
}
module := &object.Module{Name: s.Value, Attrs: attrs}
return vm.push(module)
}
func (vm *VM) LastPoppedStackElem() object.Object {
return vm.stack[vm.sp]
}
func (vm *VM) Run() error {
var ip int
var ins code.Instructions
var op code.Opcode
if vm.Debug {
log.Printf(
"%-25s %-20s\n",
fmt.Sprintf(
"%04d %s", ip,
strings.Split(ins[ip:].String(), "\n")[0][4:],
),
fmt.Sprintf(
"[ip=%02d fp=%02d, sp=%02d]",
ip, vm.fp-1, vm.sp,
),
)
}
for vm.frame.ip < len(vm.frame.Instructions())-1 {
vm.frame.ip++
ip = vm.frame.ip
ins = vm.frame.Instructions()
op = code.Opcode(ins[ip])
switch op {
case code.OpConstant:
constIndex := code.ReadUint16(ins[ip+1:])
vm.frame.ip += 2
err := vm.push(vm.state.Constants[constIndex])
if err != nil {
return err
}
case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv, code.OpMod, code.OpOr,
code.OpAnd, code.OpBitwiseOR, code.OpBitwiseXOR, code.OpBitwiseAND,
code.OpLeftShift, code.OpRightShift:
err := vm.executeBinaryOperation(op)
if err != nil {
return err
}
case code.OpPop:
vm.pop()
case code.OpTrue:
err := vm.push(True)
if err != nil {
return err
}
case code.OpFalse:
err := vm.push(False)
if err != nil {
return err
}
case code.OpEqual, code.OpNotEqual, code.OpGreaterThan, code.OpGreaterThanEqual:
err := vm.executeComparison(op)
if err != nil {
return err
}
case code.OpNot:
err := vm.executeNotOperator()
if err != nil {
return err
}
case code.OpBitwiseNOT:
err := vm.executeBitwiseNotOperator()
if err != nil {
return err
}
case code.OpMinus:
err := vm.executeMinusOperator()
if err != nil {
return err
}
case code.OpJump:
pos := int(code.ReadUint16(ins[ip+1:]))
vm.frame.ip = pos - 1
case code.OpJumpNotTruthy:
pos := int(code.ReadUint16(ins[ip+1:]))
vm.frame.ip += 2
condition := vm.pop()
if !isTruthy(condition) {
vm.frame.ip = pos - 1
}
case code.OpNull:
err := vm.push(Null)
if err != nil {
return err
}
case code.OpSetGlobal:
globalIndex := code.ReadUint16(ins[ip+1:])
vm.frame.ip += 2
ref := vm.pop()
if immutable, ok := ref.(object.Immutable); ok {
vm.state.Globals[globalIndex] = immutable.Clone()
} else {
vm.state.Globals[globalIndex] = ref
}
err := vm.push(Null)
if err != nil {
return err
}
case code.OpAssignGlobal:
globalIndex := code.ReadUint16(ins[ip+1:])
vm.frame.ip += 2
vm.state.Globals[globalIndex] = vm.pop()
err := vm.push(Null)
if err != nil {
return err
}
case code.OpAssignLocal:
localIndex := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1
vm.stack[vm.frame.basePointer+int(localIndex)] = vm.pop()
err := vm.push(Null)
if err != nil {
return err
}
case code.OpGetGlobal:
globalIndex := code.ReadUint16(ins[ip+1:])
vm.frame.ip += 2
err := vm.push(vm.state.Globals[globalIndex])
if err != nil {
return err
}
case code.OpArray:
numElements := int(code.ReadUint16(ins[ip+1:]))
vm.frame.ip += 2
array := vm.buildArray(vm.sp-numElements, vm.sp)
vm.sp = vm.sp - numElements
err := vm.push(array)
if err != nil {
return err
}
case code.OpHash:
numElements := int(code.ReadUint16(ins[ip+1:]))
vm.frame.ip += 2
hash, err := vm.buildHash(vm.sp-numElements, vm.sp)
if err != nil {
return err
}
vm.sp = vm.sp - numElements
err = vm.push(hash)
if err != nil {
return err
}
case code.OpSetItem:
value := vm.pop()
index := vm.pop()
left := vm.pop()
err := vm.executeSetItem(left, index, value)
if err != nil {
return err
}
case code.OpGetItem:
index := vm.pop()
left := vm.pop()
err := vm.executeGetItem(left, index)
if err != nil {
return err
}
case code.OpCall:
numArgs := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1
err := vm.executeCall(int(numArgs))
if err != nil {
return err
}
case code.OpReturn:
returnValue := vm.pop()
frame := vm.popFrame()
vm.sp = frame.basePointer - 1
err := vm.push(returnValue)
if err != nil {
return err
}
case code.OpSetLocal:
localIndex := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1
ref := vm.pop()
if immutable, ok := ref.(object.Immutable); ok {
vm.stack[vm.frame.basePointer+int(localIndex)] = immutable.Clone()
} else {
vm.stack[vm.frame.basePointer+int(localIndex)] = ref
}
err := vm.push(Null)
if err != nil {
return err
}
case code.OpGetLocal:
localIndex := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1
err := vm.push(vm.stack[vm.frame.basePointer+int(localIndex)])
if err != nil {
return err
}
case code.OpGetBuiltin:
builtinIndex := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1
builtin := builtins.BuiltinsIndex[builtinIndex]
err := vm.push(builtin)
if err != nil {
return err
}
case code.OpClosure:
constIndex := code.ReadUint16(ins[ip+1:])
numFree := code.ReadUint8(ins[ip+3:])
vm.frame.ip += 3
err := vm.pushClosure(int(constIndex), int(numFree))
if err != nil {
return err
}
case code.OpGetFree:
freeIndex := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1
err := vm.push(vm.frame.cl.Free[freeIndex])
if err != nil {
return err
}
case code.OpCurrentClosure:
currentClosure := vm.frame.cl
err := vm.push(currentClosure)
if err != nil {
return err
}
case code.OpLoadModule:
name := vm.pop()
err := vm.loadModule(name)
if err != nil {
return err
}
}
if vm.Debug {
log.Printf(
"%-25s [ip=%02d fp=%02d, sp=%02d]",
"", ip, vm.fp-1, vm.sp,
)
}
}
return nil
}
func (vm *VM) executeSetItem(left, index, value object.Object) error {
switch {
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
return vm.executeArraySetItem(left, index, value)
case left.Type() == object.HASH_OBJ:
return vm.executeHashSetItem(left, index, value)
default:
return fmt.Errorf(
"set item operation not supported: left=%s index=%s",
left.Type(), index.Type(),
)
}
}
func (vm *VM) executeGetItem(left, index object.Object) error {
switch {
case left.Type() == object.STRING_OBJ && index.Type() == object.INTEGER_OBJ:
return vm.executeStringGetItem(left, index)
case left.Type() == object.STRING_OBJ && index.Type() == object.STRING_OBJ:
return vm.executeStringIndex(left, index)
case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
return vm.executeArrayGetItem(left, index)
case left.Type() == object.HASH_OBJ:
return vm.executeHashGetItem(left, index)
case left.Type() == object.MODULE_OBJ:
return vm.executeHashGetItem(left.(*object.Module).Attrs, index)
default:
return fmt.Errorf(
"index operator not supported: left=%s index=%s",
left.Type(), index.Type(),
)
}
}
func (vm *VM) executeArrayGetItem(array, index object.Object) error {
arrayObject := array.(*object.Array)
i := index.(*object.Integer).Value
max := int64(len(arrayObject.Elements) - 1)
if i < 0 || i > max {
return vm.push(Null)
}
return vm.push(arrayObject.Elements[i])
}
func (vm *VM) executeArraySetItem(array, index, value object.Object) error {
arrayObject := array.(*object.Array)
i := index.(*object.Integer).Value
max := int64(len(arrayObject.Elements) - 1)
if i < 0 || i > max {
return fmt.Errorf("index out of bounds: %d", i)
}
arrayObject.Elements[i] = value
return vm.push(Null)
}
func (vm *VM) executeHashGetItem(hash, index object.Object) error {
hashObject := hash.(*object.Hash)
key, ok := index.(object.Hashable)
if !ok {
return fmt.Errorf("unusable as hash key: %s", index.Type())
}
pair, ok := hashObject.Pairs[key.HashKey()]
if !ok {
return vm.push(Null)
}
return vm.push(pair.Value)
}
func (vm *VM) executeHashSetItem(hash, index, value object.Object) error {
hashObject := hash.(*object.Hash)
key, ok := index.(object.Hashable)
if !ok {
return fmt.Errorf("unusable as hash key: %s", index.Type())
}
hashed := key.HashKey()
hashObject.Pairs[hashed] = object.HashPair{Key: index, Value: value}
return vm.push(Null)
}
func (vm *VM) buildHash(startIndex, endIndex int) (object.Object, error) {
hashedPairs := make(map[object.HashKey]object.HashPair)
for i := startIndex; i < endIndex; i += 2 {
key := vm.stack[i]
value := vm.stack[i+1]
pair := object.HashPair{Key: key, Value: value}
hashKey, ok := key.(object.Hashable)
if !ok {
return nil, fmt.Errorf("unusable as hash key: %s", key.Type())
}
hashedPairs[hashKey.HashKey()] = pair
}
return &object.Hash{Pairs: hashedPairs}, nil
}
func (vm *VM) buildArray(startIndex, endIndex int) object.Object {
elements := make([]object.Object, endIndex-startIndex)
for i := startIndex; i < endIndex; i++ {
elements[i-startIndex] = vm.stack[i]
}
return &object.Array{Elements: elements}
}
func isTruthy(obj object.Object) bool {
switch obj := obj.(type) {
case *object.Boolean:
return obj.Value
case *object.Null:
return false
default:
return true
}
}
func (vm *VM) push(o object.Object) error {
if vm.sp >= StackSize {
return fmt.Errorf("stack overflow")
}
vm.stack[vm.sp] = o
vm.sp++
return nil
}
func (vm *VM) pop() object.Object {
o := vm.stack[vm.sp-1]
vm.sp--
return o
}
func (vm *VM) executeBinaryOperation(op code.Opcode) error {
right := vm.pop()
left := vm.pop()
leftType := left.Type()
rightType := right.Type()
switch {
// {"a": 1} + {"b": 2}
case op == code.OpAdd && left.Type() == object.HASH_OBJ && right.Type() == object.HASH_OBJ:
leftVal := left.(*object.Hash).Pairs
rightVal := right.(*object.Hash).Pairs
pairs := make(map[object.HashKey]object.HashPair)
for k, v := range leftVal {
pairs[k] = v
}
for k, v := range rightVal {
pairs[k] = v
}
return vm.push(&object.Hash{Pairs: pairs})
// [1] + [2]
case op == code.OpAdd && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ:
leftVal := left.(*object.Array).Elements
rightVal := right.(*object.Array).Elements
elements := make([]object.Object, len(leftVal)+len(rightVal))
elements = append(leftVal, rightVal...)
return vm.push(&object.Array{Elements: elements})
// [1] * 3
case op == code.OpMul && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ:
leftVal := left.(*object.Array).Elements
rightVal := int(right.(*object.Integer).Value)
elements := leftVal
for i := rightVal; i > 1; i-- {
elements = append(elements, leftVal...)
}
return vm.push(&object.Array{Elements: elements})
// 3 * [1]
case op == code.OpMul && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ:
leftVal := int(left.(*object.Integer).Value)
rightVal := right.(*object.Array).Elements
elements := rightVal
for i := leftVal; i > 1; i-- {
elements = append(elements, rightVal...)
}
return vm.push(&object.Array{Elements: elements})
// " " * 4
case op == code.OpMul && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ:
leftVal := left.(*object.String).Value
rightVal := right.(*object.Integer).Value
return vm.push(&object.String{Value: strings.Repeat(leftVal, int(rightVal))})
// 4 * " "
case op == code.OpMul && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ:
leftVal := left.(*object.Integer).Value
rightVal := right.(*object.String).Value
return vm.push(&object.String{Value: strings.Repeat(rightVal, int(leftVal))})
case leftType == object.BOOLEAN_OBJ && rightType == object.BOOLEAN_OBJ:
return vm.executeBinaryBooleanOperation(op, left, right)
case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ:
return vm.executeBinaryIntegerOperation(op, left, right)
case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ:
return vm.executeBinaryStringOperation(op, left, right)
default:
return fmt.Errorf("unsupported types for binary operation: %s %s", leftType, rightType)
}
}
func (vm *VM) executeBinaryBooleanOperation(op code.Opcode, left, right object.Object) error {
leftValue := left.(*object.Boolean).Value
rightValue := right.(*object.Boolean).Value
var result bool
switch op {
case code.OpOr:
result = leftValue || rightValue
case code.OpAnd:
result = leftValue && rightValue
default:
return fmt.Errorf("unknown boolean operator: %d", op)
}
return vm.push(&object.Boolean{Value: result})
}
func (vm *VM) executeBinaryIntegerOperation(op code.Opcode, left, right object.Object) error {
leftValue := left.(*object.Integer).Value
rightValue := right.(*object.Integer).Value
var result int64
switch op {
case code.OpAdd:
result = leftValue + rightValue
case code.OpSub:
result = leftValue - rightValue
case code.OpMul:
result = leftValue * rightValue
case code.OpDiv:
result = leftValue / rightValue
case code.OpMod:
result = leftValue % rightValue
case code.OpBitwiseOR:
result = leftValue | rightValue
case code.OpBitwiseXOR:
result = leftValue ^ rightValue
case code.OpBitwiseAND:
result = leftValue & rightValue
case code.OpLeftShift:
result = leftValue << uint64(rightValue)
case code.OpRightShift:
result = leftValue >> uint64(rightValue)
default:
return fmt.Errorf("unknown integer operator: %d", op)
}
return vm.push(&object.Integer{Value: result})
}
func (vm *VM) executeBinaryStringOperation(op code.Opcode, left, right object.Object) error {
if op != code.OpAdd {
return fmt.Errorf("unknown string operator: %d", op)
}
leftValue := left.(*object.String).Value
rightValue := right.(*object.String).Value
return vm.push(&object.String{Value: leftValue + rightValue})
}
func (vm *VM) executeComparison(op code.Opcode) error {
right := vm.pop()
left := vm.pop()
switch op {
case code.OpEqual:
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 0))
case code.OpNotEqual:
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) != 0))
case code.OpGreaterThanEqual:
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) > -1))
case code.OpGreaterThan:
return vm.push(nativeBoolToBooleanObject(left.(object.Comparable).Compare(right) == 1))
default:
return fmt.Errorf("unknown operator: %d (%s %s)",
op, left.Type(), right.Type())
}
}
func (vm *VM) executeBitwiseNotOperator() error {
operand := vm.pop()
if i, ok := operand.(*object.Integer); ok {
return vm.push(&object.Integer{Value: ^i.Value})
}
return fmt.Errorf("expected int got=%T", operand)
}
func (vm *VM) executeNotOperator() error {
operand := vm.pop()
switch operand {
case True:
return vm.push(False)
case False:
return vm.push(True)
case Null:
return vm.push(True)
default:
return vm.push(False)
}
}
func (vm *VM) executeMinusOperator() error {
operand := vm.pop()
if i, ok := operand.(*object.Integer); ok {
return vm.push(&object.Integer{Value: -i.Value})
}
return fmt.Errorf("expected int got=%T", operand)
}
func (vm *VM) executeCall(numArgs int) error {
callee := vm.stack[vm.sp-1-numArgs]
switch callee := callee.(type) {
case *object.Closure:
return vm.callClosure(callee, numArgs)
case *object.Builtin:
return vm.callBuiltin(callee, numArgs)
default:
return fmt.Errorf("calling non-function and non-built-in")
}
}
func (vm *VM) callClosure(cl *object.Closure, numArgs int) error {
if numArgs != cl.Fn.NumParameters {
return fmt.Errorf("wrong number of arguments: want=%d, got=%d", cl.Fn.NumParameters, numArgs)
}
// Optimize tail calls and avoid a new frame
if cl.Fn == vm.frame.cl.Fn {
nextOP := vm.frame.NextOp()
if nextOP == code.OpReturn {
for p := 0; p < numArgs; p++ {
vm.stack[vm.frame.basePointer+p] = vm.stack[vm.sp-numArgs+p]
}
vm.sp -= numArgs + 1
vm.frame.ip = -1 // reset IP to the beginning of the frame
return nil
}
}
frame := NewFrame(cl, vm.sp-numArgs)
vm.pushFrame(frame)
vm.sp = frame.basePointer + cl.Fn.NumLocals
return nil
}
func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error {
args := vm.stack[vm.sp-numArgs : vm.sp]
result := builtin.Fn(args...)
vm.sp = vm.sp - numArgs - 1
if result != nil {
err := vm.push(result)
if err != nil {
return err
}
} else {
err := vm.push(Null)
if err != nil {
return err
}
}
return nil
}
func (vm *VM) pushClosure(constIndex, numFree int) error {
constant := vm.state.Constants[constIndex]
function, ok := constant.(*object.CompiledFunction)
if !ok {
return fmt.Errorf("not a function %+v", constant)
}
free := make([]object.Object, numFree)
for i := 0; i < numFree; i++ {
free[i] = vm.stack[vm.sp-numFree+i]
}
vm.sp = vm.sp - numFree
closure := &object.Closure{Fn: function, Free: free}
return vm.push(closure)
}
func (vm *VM) executeStringGetItem(str, index object.Object) error {
stringObject := str.(*object.String)
i := index.(*object.Integer).Value
max := int64(len(stringObject.Value) - 1)
if i < 0 || i > max {
return vm.push(&object.String{Value: ""})
}
return vm.push(&object.String{Value: string(stringObject.Value[i])})
}
func (vm *VM) executeStringIndex(str, index object.Object) error {
stringObject := str.(*object.String)
substr := index.(*object.String).Value
return vm.push(
&object.Integer{
Value: int64(strings.Index(stringObject.Value, substr)),
},
)
}
func nativeBoolToBooleanObject(input bool) *object.Boolean {
if input {
return True
}
return False
}

1215
internal/vm/vm_test.go Normal file

File diff suppressed because it is too large Load Diff