Skip to content

Commit

Permalink
feat: add a builtin function type to the object system (#14)
Browse files Browse the repository at this point in the history
## What does this PR do?

add a builtin function type to the object system
  • Loading branch information
Aden-Q authored May 12, 2024
1 parent 81a4fd5 commit 726947d
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ concurrency:
cancel-in-progress: true

jobs:
lint-build-test:
ci:
name: lint-build-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
tag-release:
if: github.event.pull_request.merged == true
Expand Down
47 changes: 26 additions & 21 deletions internal/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,15 @@ func (e *evaluator) evalReturnStatement(stmt *ast.ReturnStatement) (object.Objec
}

func (e *evaluator) evalIdentifierExpression(ie *ast.IdentifierExpression) (object.Object, error) {
val, ok := e.env.Get(ie.Value)
if !ok {
return object.NIL, ErrIdentifierNotFound
if val, ok := e.env.Get(ie.Value); ok {
return val, nil
}

return val, nil
if builtinFunc, ok := object.BuiltinFuncs[ie.Value]; ok {
return builtinFunc, nil
}

return object.NIL, ErrIdentifierNotFound
}

func (e *evaluator) evalIfExpression(ie *ast.IfExpression) (object.Object, error) {
Expand Down Expand Up @@ -164,26 +167,28 @@ func (e *evaluator) evalCallExpression(ce *ast.CallExpression) (object.Object, e
}

func applyFunc(fn object.Object, args []object.Object, env object.Environment) (object.Object, error) {
function, ok := fn.(*object.Func)
if !ok {
return object.NIL, ErrNotAFunction
}

// create a new environment for the function scope
funcEvaluator := New(object.NewClosureEnvironment(env))
// extend the closure environment with arguments passed to the function
funcEvaluator.extendFunctionEnv(function, args)
switch fn := fn.(type) {
case *object.Func:
// create a new environment for the function scope
funcEvaluator := New(object.NewClosureEnvironment(env))
// extend the closure environment with arguments passed to the function
funcEvaluator.extendFunctionEnv(fn, args)

val, err := funcEvaluator.Eval(fn.Body)
if err != nil {
return object.NIL, err
}

val, err := funcEvaluator.Eval(function.Body)
if err != nil {
return object.NIL, err
}
if returnVal, ok := val.(*object.ReturnValue); ok {
return returnVal.Value, nil
}

if returnVal, ok := val.(*object.ReturnValue); ok {
return returnVal.Value, nil
return val, nil
case object.BuiltinFunc:
return fn(args...)
default:
return object.NIL, ErrNotAFunction
}

return val, nil
}

func (e *evaluator) extendFunctionEnv(fn *object.Func, args []object.Object) {
Expand Down
53 changes: 53 additions & 0 deletions internal/evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,59 @@ var _ = Describe("Evaluator", func() {
Expect(obj).To(Equal(expectedObject))
})
})

Context("builtin functions", func() {
It("length of an empty string", func() {
text = `
len("");
`
expectedObject := object.NewInteger(0)
expectedParseErrors := []error{}

// parse the program
program, errs = p.ParseProgram(text)
Expect(errs).To(Equal(expectedParseErrors))

// evaluate the AST tree
obj, err := e.Eval(program)
Expect(err).ToNot(HaveOccurred())
Expect(obj).To(Equal(expectedObject))
})

It("length of a non-empty string", func() {
text = `
len("hello world");
`
expectedObject := object.NewInteger(11)
expectedParseErrors := []error{}

// parse the program
program, errs = p.ParseProgram(text)
Expect(errs).To(Equal(expectedParseErrors))

// evaluate the AST tree
obj, err := e.Eval(program)
Expect(err).ToNot(HaveOccurred())
Expect(obj).To(Equal(expectedObject))
})

It("length on integer unsupported", func() {
text = `
len(1);
`
expectedObject := object.NIL
expectedParseErrors := []error{}

// parse the program
program, errs = p.ParseProgram(text)
Expect(errs).To(Equal(expectedParseErrors))

// evaluate the AST tree
obj, err := e.Eval(program)
Expect(err).To(HaveOccurred())
Expect(obj).To(Equal(expectedObject))
})
})
})
})
})
10 changes: 10 additions & 0 deletions internal/object/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package object

import (
"errors"
)

var (
ErrWrongNumberArguments = errors.New("wrong number of argument(s)")
ErrUnsupportedArgumentType = errors.New("unsupported argument type")
)
35 changes: 34 additions & 1 deletion internal/object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var _ Object = (*Nil)(nil)
var _ Object = (*ReturnValue)(nil)
var _ Object = (*Error)(nil)
var _ Object = (*Func)(nil)
var _ Object = (BuiltinFunc)(nil)

type ObjectType string

Expand All @@ -26,6 +27,7 @@ var (
RETURN_VALUE_OBJ = ObjectType("RETURN_VALUE")
ERROR_OBJ = ObjectType("ERROR")
FUNCTION_OBJ = ObjectType("FUNCTION")
BUILTINFUNC_OBJ = ObjectType("BUILTINFUNC")
)

// boolean literal objects
Expand All @@ -35,6 +37,21 @@ var (
NIL = NewNil()
)

var BuiltinFuncs = map[string]BuiltinFunc{
"len": func(args ...Object) (Object, error) {
if len(args) != 1 {
return NIL, ErrWrongNumberArguments
}

switch arg := args[0].(type) {
case *String:
return NewInteger(int64(len(arg.Value))), nil
default:
return NIL, ErrUnsupportedArgumentType
}
},
}

type Object interface {
Type() ObjectType
Inspect() string
Expand Down Expand Up @@ -210,7 +227,23 @@ func (f *Func) Inspect() string {
return builder.String()
}

// FIXME: this behavior is undined, not sure an error is truthy or not
// FIXME: this behavior is undined, not sure whether a function is truthy or not
func (f *Func) IsTruthy() bool {
return false
}

// BuiltinFunc represents a builtin function object
type BuiltinFunc func(args ...Object) (Object, error)

func (b BuiltinFunc) Type() ObjectType {
return FUNCTION_OBJ
}

func (b BuiltinFunc) Inspect() string {
return "builtin function"
}

// FIXME: this behavior is undined, not sure whether a builtin function is truthy or not
func (b BuiltinFunc) IsTruthy() bool {
return false
}

0 comments on commit 726947d

Please sign in to comment.