Skip to content

Commit

Permalink
Extending the interpreter (#4)
Browse files Browse the repository at this point in the history
* add support for strings

* add support for concatenating strings

* add support for builtin functions: len

* add support for arrays and indexing

* add some more builtin functions for arrays

* add support for hashes

* add builtin puts function
  • Loading branch information
juandspy authored Dec 21, 2023
1 parent 811cec4 commit b6d53d6
Show file tree
Hide file tree
Showing 11 changed files with 871 additions and 25 deletions.
66 changes: 66 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,69 @@ func (ce *CallExpression) String() string {
out.WriteString(")")
return out.String()
}

type StringLiteral struct {
Token token.Token
Value string
}

func (sl *StringLiteral) expressionNode() {}
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
func (sl *StringLiteral) String() string { return sl.Token.Literal }

type ArrayLiteral struct {
Token token.Token // the '[' token
Elements []Expression
}

func (al *ArrayLiteral) expressionNode() {}
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()
}

// IndexExpression supports operations like myArray[0]
type IndexExpression struct {
Token token.Token // The [ token
Left Expression
Index Expression
}

func (ie *IndexExpression) expressionNode() {}
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()
}

type HashLiteral struct {
Token token.Token // the '{' token
Pairs map[Expression]Expression
}

func (hl *HashLiteral) expressionNode() {}
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()
}
103 changes: 103 additions & 0 deletions evaluator/builtins.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package evaluator

import (
"fmt"

"github.com/juandspy/monkey-lang/object"
)

var builtins = map[string]*object.Builtin{
"len": {
Fn: func(args ...object.Object) object.Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1", len(args))
}
switch arg := args[0].(type) {
case *object.String:
return &object.Integer{Value: int64(len(arg.Value))}
case *object.Array:
return &object.Integer{Value: int64(len(arg.Elements))}
default:
return newError("argument to `len` not supported, got %s", args[0].Type())
}
},
},
"first": {
Fn: func(args ...object.Object) object.Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1",
len(args))
}
if args[0].Type() != object.ARRAY_OBJ {
return newError("argument to `first` must be ARRAY, got %s", args[0].Type())
}
arr := args[0].(*object.Array)
if len(arr.Elements) > 0 {
return arr.Elements[0]
}
return NULL
},
},
"last": {
Fn: func(args ...object.Object) object.Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1",
len(args))
}
if args[0].Type() != object.ARRAY_OBJ {
return newError("argument to `last` must be ARRAY, got %s", args[0].Type())
}
arr := args[0].(*object.Array)
length := len(arr.Elements)
if len(arr.Elements) > 0 {
return arr.Elements[length-1]
}
return NULL
},
},
// returns all the elements except the first one
"rest": {
Fn: func(args ...object.Object) object.Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1",
len(args))
}
if args[0].Type() != object.ARRAY_OBJ {
return newError("argument to `rest` must be ARRAY, got %s", args[0].Type())
}
arr := args[0].(*object.Array)
length := len(arr.Elements)
if length > 0 {
newElements := make([]object.Object, length-1, length-1)
copy(newElements, arr.Elements[1:length])
return &object.Array{Elements: newElements}
}
return NULL
},
},
"push": {
Fn: func(args ...object.Object) object.Object {
if len(args) != 2 {
return newError("wrong number of arguments. got=%d, want=2",
len(args))
}
if args[0].Type() != object.ARRAY_OBJ {
return newError("argument to `push` must be ARRAY, got %s", args[0].Type())
}
arr := args[0].(*object.Array)
length := len(arr.Elements)
newElements := make([]object.Object, length+1, length+1)
copy(newElements, arr.Elements)
newElements[length] = args[1]
return &object.Array{Elements: newElements}
},
},
"puts": {
Fn: func(args ...object.Object) object.Object {
for _, arg := range args {
fmt.Println(arg.Inspect())
}
return NULL
},
},
}
115 changes: 106 additions & 9 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
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
}
Expand Down Expand Up @@ -157,6 +177,8 @@ func evalInfixExpression(
return nativeBoolToBooleanObject(left == right)
case operator == "!=":
return nativeBoolToBooleanObject(left != right)
case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
return evalStringInfixExpression(operator, left, right)
case left.Type() != right.Type():
return newError("type mismatch: %s %s %s",
left.Type(), operator, right.Type())
Expand Down Expand Up @@ -193,6 +215,18 @@ func evalIntegerInfixExpression(operator string,
}
}

func evalStringInfixExpression(operator string,
left, right object.Object,
) object.Object {
if operator != "+" {
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
leftVal := left.(*object.String).Value
rightVal := right.(*object.String).Value
return &object.String{Value: leftVal + rightVal}

}

func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object {
condition := Eval(ie.Condition, env)
if isError(condition) {
Expand All @@ -210,11 +244,13 @@ func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Obje
func evalIdentifier(
node *ast.Identifier, env *object.Environment,
) object.Object {
val, ok := env.Get(node.Value)
if !ok {
return newError("identifier not found: " + node.Value)
if val, ok := env.Get(node.Value); ok {
return val
}
return val
if builtin, ok := builtins[node.Value]; ok {
return builtin
}
return newError("identifier not found: " + node.Value)
}

func evalExpressions(
Expand All @@ -231,14 +267,51 @@ func evalExpressions(
return result
}

func applyFunction(fn object.Object, args []object.Object) object.Object {
function, ok := fn.(*object.Function)
func evalIndexExpression(left, index object.Object) object.Object {
switch {
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)
default:
return newError("index operator not supported: %s", left.Type())
}
}

func evalArrayIndexExpression(array, index object.Object) object.Object {
arrayObject := array.(*object.Array)
idx := index.(*object.Integer).Value
max := int64(len(arrayObject.Elements) - 1)
if idx < 0 || idx > max {
return NULL
}
return arrayObject.Elements[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 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:
return fn.Fn(args...)
default:
return newError("not a function: %s", fn.Type())
}
extendedEnv := extendFunctionEnv(function, args)
evaluated := Eval(function.Body, extendedEnv)
return unwrapReturnValue(evaluated)
}
func extendFunctionEnv(fn *object.Function, args []object.Object,
) *object.Environment {
Expand All @@ -248,6 +321,30 @@ func extendFunctionEnv(fn *object.Function, args []object.Object,
}
return env
}

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}
}

func unwrapReturnValue(obj object.Object) object.Object {
if returnValue, ok := obj.(*object.ReturnValue); ok {
return returnValue.Value
Expand Down
Loading

0 comments on commit b6d53d6

Please sign in to comment.