restructure project
This commit is contained in:
452
internal/ast/ast.go
Normal file
452
internal/ast/ast.go
Normal 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
30
internal/ast/ast_test.go
Normal 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
24
internal/builtins/abs.go
Normal 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}
|
||||
}
|
||||
32
internal/builtins/accept.go
Normal file
32
internal/builtins/accept.go
Normal 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
22
internal/builtins/args.go
Normal 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}
|
||||
}
|
||||
26
internal/builtins/assert.go
Normal file
26
internal/builtins/assert.go
Normal 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
22
internal/builtins/bin.go
Normal 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
54
internal/builtins/bind.go
Normal 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
18
internal/builtins/bool.go
Normal 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()}
|
||||
}
|
||||
77
internal/builtins/builtins.go
Normal file
77
internal/builtins/builtins.go
Normal 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
21
internal/builtins/chr.go
Normal 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))}
|
||||
}
|
||||
27
internal/builtins/close.go
Normal file
27
internal/builtins/close.go
Normal 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{}
|
||||
}
|
||||
50
internal/builtins/connect.go
Normal file
50
internal/builtins/connect.go
Normal 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{}
|
||||
}
|
||||
24
internal/builtins/divmod.go
Normal file
24
internal/builtins/divmod.go
Normal 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
26
internal/builtins/exit.go
Normal 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
37
internal/builtins/ffi.go
Normal 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
52
internal/builtins/find.go
Normal 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(),
|
||||
)
|
||||
}
|
||||
24
internal/builtins/first.go
Normal file
24
internal/builtins/first.go
Normal 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
22
internal/builtins/hash.go
Normal 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
22
internal/builtins/hex.go
Normal 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
41
internal/builtins/id.go
Normal 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
|
||||
}
|
||||
34
internal/builtins/input.go
Normal file
34
internal/builtins/input.go
Normal 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
35
internal/builtins/int.go
Normal 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
26
internal/builtins/join.go
Normal 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
25
internal/builtins/last.go
Normal 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
21
internal/builtins/len.go
Normal 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())
|
||||
}
|
||||
27
internal/builtins/listen.go
Normal file
27
internal/builtins/listen.go
Normal 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{}
|
||||
}
|
||||
21
internal/builtins/lower.go
Normal file
21
internal/builtins/lower.go
Normal 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
31
internal/builtins/max.go
Normal 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
31
internal/builtins/min.go
Normal 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
22
internal/builtins/oct.go
Normal 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
86
internal/builtins/open.go
Normal 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
26
internal/builtins/ord.go
Normal 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
29
internal/builtins/pop.go
Normal 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
34
internal/builtins/pow.go
Normal 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}
|
||||
}
|
||||
22
internal/builtins/print.go
Normal file
22
internal/builtins/print.go
Normal 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
22
internal/builtins/push.go
Normal 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
40
internal/builtins/read.go
Normal 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])}
|
||||
}
|
||||
26
internal/builtins/readfile.go
Normal file
26
internal/builtins/readfile.go
Normal 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
22
internal/builtins/rest.go
Normal 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
|
||||
}
|
||||
22
internal/builtins/reversed.go
Normal file
22
internal/builtins/reversed.go
Normal 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
37
internal/builtins/seek.go
Normal 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}
|
||||
}
|
||||
65
internal/builtins/socket.go
Normal file
65
internal/builtins/socket.go
Normal 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)}
|
||||
}
|
||||
68
internal/builtins/socket_utils.go
Normal file
68
internal/builtins/socket_utils.go
Normal 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
|
||||
}
|
||||
23
internal/builtins/sorted.go
Normal file
23
internal/builtins/sorted.go
Normal 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
|
||||
}
|
||||
32
internal/builtins/split.go
Normal file
32
internal/builtins/split.go
Normal 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
18
internal/builtins/str.go
Normal 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()}
|
||||
}
|
||||
18
internal/builtins/typeof.go
Normal file
18
internal/builtins/typeof.go
Normal 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())}
|
||||
}
|
||||
20
internal/builtins/upper.go
Normal file
20
internal/builtins/upper.go
Normal 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)}
|
||||
}
|
||||
28
internal/builtins/write.go
Normal file
28
internal/builtins/write.go
Normal 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)}
|
||||
}
|
||||
28
internal/builtins/writefile.go
Normal file
28
internal/builtins/writefile.go
Normal 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
222
internal/code/code.go
Normal 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]
|
||||
}
|
||||
88
internal/code/code_test.go
Normal file
88
internal/code/code_test.go
Normal 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])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
651
internal/compiler/compiler.go
Normal file
651
internal/compiler/compiler.go
Normal 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)
|
||||
}
|
||||
}
|
||||
1204
internal/compiler/compiler_test.go
Normal file
1204
internal/compiler/compiler_test.go
Normal file
File diff suppressed because it is too large
Load Diff
93
internal/compiler/symbol_table.go
Normal file
93
internal/compiler/symbol_table.go
Normal 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
|
||||
}
|
||||
350
internal/compiler/symbol_table_test.go
Normal file
350
internal/compiler/symbol_table_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
685
internal/evaluator/evaluator.go
Normal file
685
internal/evaluator/evaluator.go
Normal 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}
|
||||
}
|
||||
964
internal/evaluator/evaluator_test.go
Normal file
964
internal/evaluator/evaluator_test.go
Normal 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
287
internal/lexer/lexer.go
Normal 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]
|
||||
}
|
||||
212
internal/lexer/lexer_test.go
Normal file
212
internal/lexer/lexer_test.go
Normal 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
112
internal/object/array.go
Normal 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
43
internal/object/bool.go
Normal 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
|
||||
}
|
||||
24
internal/object/builtin.go
Normal file
24
internal/object/builtin.go
Normal 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()
|
||||
}
|
||||
49
internal/object/closure.go
Normal file
49
internal/object/closure.go
Normal 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()
|
||||
}
|
||||
47
internal/object/environment.go
Normal file
47
internal/object/environment.go
Normal 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
25
internal/object/error.go
Normal 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
|
||||
}
|
||||
63
internal/object/function.go
Normal file
63
internal/object/function.go
Normal 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
102
internal/object/hash.go
Normal 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
41
internal/object/int.go
Normal 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
29
internal/object/module.go
Normal 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
26
internal/object/null.go
Normal 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
60
internal/object/object.go
Normal 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
|
||||
22
internal/object/object_test.go
Normal file
22
internal/object/object_test.go
Normal 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
10
internal/object/state.go
Normal 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
48
internal/object/str.go
Normal 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
621
internal/parser/parser.go
Normal 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
|
||||
}
|
||||
1249
internal/parser/parser_test.go
Normal file
1249
internal/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load Diff
32
internal/parser/parser_tracing.go
Normal file
32
internal/parser/parser_tracing.go
Normal 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()
|
||||
}
|
||||
8
internal/plugins/hello.go
Normal file
8
internal/plugins/hello.go
Normal 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
257
internal/repl/repl.go
Normal 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
108
internal/token/token.go
Normal 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
56
internal/typing/typing.go
Normal 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
53
internal/utils/utils.go
Normal 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
18
internal/version.go
Normal 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
270
internal/vm/cache.go
Normal 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
46
internal/vm/frame.go
Normal 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
928
internal/vm/vm.go
Normal 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
1215
internal/vm/vm_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user